import type { Theme } from '../theme.js' export type Token = [string, string] interface LangSpec { comment: null | string keywords: Set } const KW = (s: string) => new Set(s.split(/\s+/).filter(Boolean)) const TS = KW(` abstract as async await break case catch class const continue debugger default delete do else enum export extends false finally for from function get if implements import in instanceof interface is let new null of package private protected public readonly return set static super switch this throw true try type typeof undefined var void while with yield `) const PY = KW(` False None True and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield `) const SH = KW(` if then else elif fi for in do done while until case esac function return break continue local export readonly declare typeset `) const GO = KW(` break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var nil true false `) const RUST = KW(` as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while yield `) const SQL = KW(` select from where and or not in is null as by group order limit offset insert into values update set delete create table drop alter add column primary key foreign references join left right inner outer on `) const LANGS: Record = { go: { comment: '//', keywords: GO }, json: { comment: null, keywords: KW('true false null') }, py: { comment: '#', keywords: PY }, rust: { comment: '//', keywords: RUST }, sh: { comment: '#', keywords: SH }, sql: { comment: '--', keywords: SQL }, ts: { comment: '//', keywords: TS }, yaml: { comment: '#', keywords: KW('true false null yes no on off') } } const ALIAS: Record = { bash: 'sh', javascript: 'ts', js: 'ts', jsx: 'ts', python: 'py', rs: 'rust', shell: 'sh', tsx: 'ts', typescript: 'ts', yml: 'yaml', zsh: 'sh' } const resolve = (lang: string): LangSpec | null => LANGS[ALIAS[lang] ?? lang] ?? null export const isHighlightable = (lang: string): boolean => resolve(lang) !== null const TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g export function highlightLine(line: string, lang: string, t: Theme): Token[] { const spec = resolve(lang) if (!spec) { return [['', line]] } if (spec.comment && line.trimStart().startsWith(spec.comment)) { return [[t.color.dim, line]] } const tokens: Token[] = [] let last = 0 for (const m of line.matchAll(TOKEN_RE)) { const start = m.index ?? 0 if (start > last) { tokens.push(['', line.slice(last, start)]) } const tok = m[0] const ch = tok[0]! if (ch === '"' || ch === "'" || ch === '`') { tokens.push([t.color.amber, tok]) } else if (ch >= '0' && ch <= '9') { tokens.push([t.color.cornsilk, tok]) } else if (spec.keywords.has(tok)) { tokens.push([t.color.bronze, tok]) } else { tokens.push(['', tok]) } last = start + tok.length } if (last < line.length) { tokens.push(['', line.slice(last)]) } return tokens }