char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
bc405c3
main
1import { highlightedLines } from "./highlight.ts" ; 2import { detectLanguage , languageNamed } from "./languages.ts" ; 3 4// as bare jsx text, a newline would collapse 5const NEWLINE = "\n" ; 6 7const waiting = new WeakMap < Element , () => void >(); 8let visibility :IntersectionObserver | undefined ; 9 10export type CodeOptions = { 11/** Picks the grammar by file name. */ 12readonly path ?:string ; 13/** A grammar or an alias such as `ts`; wins over `path`. */ 14readonly language ?:string ; 15/** What each line's gutter shows: line numbers by default, `false` for no gutter. */ 16readonly gutter ?:(( index :number ) => string ) | false ; 17}; 18 19const grammarOf = ({ path, language} :Pick < CodeOptions , "path" | "language" >) => 20language ?languageNamed ( language ) ?? language :path ?detectLanguage ( path ) :null ; 21 22/** `source`'s lines highlighted, the grammar picked as `code` picks it; `null` without one. */ 23export async function highlight ( 24source :string , 25options :Pick < CodeOptions , "path" | "language" >, 26) :Promise < Node [][] | null > { 27const grammar = grammarOf ( options ); 28return grammar === null ?null :await highlightedLines ( grammar , source ); 29} 30 31/** 32* Source as sorcery shows it: each line shares a grid row with its gutter, so wrapped lines don't displace the 33* gutter below. Plain at first, then highlighted once the worker answers. 34*/ 35export function code ( 36source :string , 37{ path, language, gutter= ( i ) => String ( i + 1 ) } :CodeOptions = {}, 38) :HTMLElement { 39const lines = source . split ( "\n" ); 40if ( lines . length > 1 && lines . at ( - 1 ) === "" ) lines . pop (); 41// the newline belongs inside the line's own text: a grid item, firefox's copy adds a break after each 42const pre = ( 43< pre class = { gutter ?"code" :"code bare" }> 44{ lines . map (( line , i ) => ( 45< span class = "code-line" > 46{ gutter ?< span class = "ln" >{ gutter ( i )}< / span> : undefined} 47< span class = "code-text" >{ line }{ i < lines . length - 1 ? NEWLINE : "" }</ span > 48< / span > 49)) } 50</ pre > 51) as HTMLPreElement ; 52const grammar = grammarOf ({ path , language }); 53if ( grammar !== null ) { 54if ( ! visibility ) { 55const observer = visibility = new IntersectionObserver (( entries ) => { 56for ( const { target , isIntersecting } of entries ) { 57if ( ! isIntersecting || ! target . isConnected ) continue ; 58const highlight = waiting . get ( target ); 59observer . unobserve ( target ); 60waiting . delete ( target ); 61highlight ?. (); 62} 63}); 64// code is built detached; observe only while mounted so discarded views aren't retained 65new MutationObserver (( records ) => { 66for ( const record of records ) { 67for ( const node of [ ... record . addedNodes , ... record . removedNodes ]) { 68if ( ! ( node instanceof Element )) continue ; 69const blocks = [ node , ... node . querySelectorAll ( "pre.code" )]; 70for ( const block of blocks ) { 71if ( ! waiting . has ( block )) continue ; 72if ( block . isConnected ) observer . observe ( block ); 73else observer . unobserve ( block ); 74} 75} 76} 77}). observe ( document , { childList : true , subtree : true }); 78} 79waiting . set ( pre , () => { 80highlightedLines ( grammar , lines . join ( "\n" )) 81. then (( highlighted ) => { 82if ( highlighted === null ) return ; 83const targets = pre . querySelectorAll ( ".code-text" ); 84for ( const [ i , line ] of highlighted . entries ()) { 85targets [ i ]?. replaceChildren ( ... line , ... ( i < highlighted . length - 1 ? [ NEWLINE ] : [])); 86} 87}) 88. catch (( error ) => console . warn ( "arborium:" , error )); 89}); 90} 91return pre ; 92}