import { escapeHTML } from './utils.js';
/**
* @typedef {object} Renderer
* @property {(text: string) => void} addText
* @property {(node: Node) => void} openNode
* @property {(node: Node) => void} closeNode
* @property {() => string} value
*/
/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */
/** @typedef {{walk: (r: Renderer) => void}} Tree */
/** */
const SPAN_CLOSE = '';
/**
* Determines if a node needs to be wrapped in
*
* @param {Node} node */
const emitsWrappingTags = (node) => {
// rarely we can have a sublanguage where language is undefined
// TODO: track down why
return !!node.scope;
};
/**
*
* @param {string} name
* @param {{prefix:string}} options
*/
const scopeToCSSClass = (name, { prefix }) => {
// sub-language
if (name.startsWith("language:")) {
return name.replace("language:", "language-");
}
// tiered scope: comment.line
if (name.includes(".")) {
const pieces = name.split(".");
return [
`${prefix}${pieces.shift()}`,
...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`))
].join(" ");
}
// simple scope
return `${prefix}${name}`;
};
/** @type {Renderer} */
export default class HTMLRenderer {
/**
* Creates a new HTMLRenderer
*
* @param {Tree} parseTree - the parse tree (must support `walk` API)
* @param {{classPrefix: string}} options
*/
constructor(parseTree, options) {
this.buffer = "";
this.classPrefix = options.classPrefix;
parseTree.walk(this);
}
/**
* Adds texts to the output stream
*
* @param {string} text */
addText(text) {
this.buffer += escapeHTML(text);
}
/**
* Adds a node open to the output stream (if needed)
*
* @param {Node} node */
openNode(node) {
if (!emitsWrappingTags(node)) return;
const className = scopeToCSSClass(node.scope,
{ prefix: this.classPrefix });
this.span(className);
}
/**
* Adds a node close to the output stream (if needed)
*
* @param {Node} node */
closeNode(node) {
if (!emitsWrappingTags(node)) return;
this.buffer += SPAN_CLOSE;
}
/**
* returns the accumulated buffer
*/
value() {
return this.buffer;
}
// helpers
/**
* Builds a span element
*
* @param {string} className */
span(className) {
this.buffer += ``;
}
}