Something went wrong. Try again.
more up to date at codeberg https://codeberg.org/cowie/tau.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147import hljs from "highlight.js/lib/index.js";import { decodeHtmlEntityAt } from "./html.ts";
export type HighlightFormatter = (text: string) => string;export type HighlightTheme = Partial<Record<string, HighlightFormatter>>;
export interface HighlightOptions { language?: string; ignoreIllegals?: boolean; languageSubset?: string[]; theme?: HighlightTheme;}
const SPAN_CLOSE = "</span>";const HIGHLIGHT_CLASS_PREFIX = "hljs-";
function getScopeFromSpanTag(tag: string): string | undefined { const match = /\sclass\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(tag); const classValue = match?.[1] ?? match?.[2]; if (!classValue) { return undefined; }
for (const className of classValue.split(/\s+/)) { if (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) { return className.slice(HIGHLIGHT_CLASS_PREFIX.length); } }
return undefined;}
function getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined { const exact = theme[scope]; if (exact) { return exact; }
const dotIndex = scope.indexOf("."); if (dotIndex !== -1) { const prefixFormatter = theme[scope.slice(0, dotIndex)]; if (prefixFormatter) { return prefixFormatter; } }
const dashIndex = scope.indexOf("-"); if (dashIndex !== -1) { const prefixFormatter = theme[scope.slice(0, dashIndex)]; if (prefixFormatter) { return prefixFormatter; } }
return undefined;}
function getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined { for (let i = scopes.length - 1; i >= 0; i--) { const scope = scopes[i]; if (!scope) { continue; } const formatter = getScopeFormatter(scope, theme); if (formatter) { return formatter; } } return theme.default;}
function isSpanOpenTagStart(html: string, index: number): boolean { if (!html.startsWith("<span", index)) { return false; } const nextChar = html[index + "<span".length]; return nextChar === ">" || nextChar === " " || nextChar === "\t" || nextChar === "\n" || nextChar === "\r";}
export function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string { let output = ""; let textBuffer = ""; const scopes: Array<string | undefined> = [];
const flushText = () => { if (!textBuffer) { return; } const formatter = getActiveFormatter(scopes, theme); output += formatter ? formatter(textBuffer) : textBuffer; textBuffer = ""; };
let index = 0; while (index < html.length) { if (isSpanOpenTagStart(html, index)) { const tagEndIndex = html.indexOf(">", index + 5); if (tagEndIndex !== -1) { flushText(); const tag = html.slice(index, tagEndIndex + 1); const scope = getScopeFromSpanTag(tag); scopes.push(scope); index = tagEndIndex + 1; continue; } }
if (html.startsWith(SPAN_CLOSE, index)) { flushText(); if (scopes.length > 0) { scopes.pop(); } index += SPAN_CLOSE.length; continue; }
if (html[index] === "&") { const decoded = decodeHtmlEntityAt(html, index); if (decoded) { textBuffer += decoded.text; index += decoded.length; continue; } }
textBuffer += html[index]; index++; }
flushText(); return output;}
export function highlight(code: string, options: HighlightOptions = {}): string { const html = options.language ? hljs.highlight(code, { language: options.language, ignoreIllegals: options.ignoreIllegals, }).value : hljs.highlightAuto(code, options.languageSubset).value; return renderHighlightedHtml(html, options.theme);}
export function supportsLanguage(name: string): boolean { return hljs.getLanguage(name) !== undefined;}