prism-code-editor
Version:
Lightweight, extensible code editor component for the web using Prism
167 lines (166 loc) • 7.48 kB
JavaScript
import { a as languages, i as highlightTokens, l as tokenizeText, n as escapeHtml } from "../core-8vQkh0Rd.js";
import { getIndentGuides } from "../extensions/guides.js";
import { t as testBracket } from "../bracket-Dr-UYgrN.js";
//#region src/ssr/brackets.ts
var stack = [];
var sp;
var addAlias = (token, newAlias = "bracket-error") => {
let alias = token.alias;
token.alias = alias ? alias + " " + newAlias : newAlias;
};
var matchRecursive = (tokens, pairs) => {
let token;
let i = 0;
for (; token = tokens[i++];) {
if (typeof token == "string") continue;
let content = token.content;
let alias = token.alias;
if (Array.isArray(content)) matchRecursive(content, pairs);
else if ((alias || token.type) == "punctuation") {
let bracketType = testBracket(content, pairs, token.length - 1);
if (bracketType) if (bracketType % 2) stack[sp++] = [token, bracketType + 1];
else {
let i = sp;
let entry;
while (entry = stack[--i]) if (bracketType == entry[1]) {
let alias = "bracket-level-" + i % 12;
let j = i;
while (++j < sp) addAlias(stack[j][0]);
addAlias(token, alias);
addAlias(entry[0], alias);
sp = i;
break;
}
if (!entry) addAlias(token);
}
}
}
};
/**
* Function that runs the same bracket matching algorithm as the {@link matchBrackets}
* extension. This is useful to add rainbow brackets outside an editor or with
* {@link renderEditor}.
*
* @param pairs Which characters to match together. The opening character must be followed
* by the corresponding closing character. Defaults to `"()[]{}"`.
* @returns A function that accepts a token stream and adds extra classes to the brackets.
*/
var rainbowBrackets = (pairs = "()[]{}") => {
return (tokens) => {
sp = 0;
matchRecursive(tokens, pairs);
stack = [];
};
};
//#endregion
//#region src/ssr/utils.ts
var escapeQuotes = (html) => {
return escapeHtml(html, /"/g, """);
};
//#endregion
//#region src/ssr/code-block.ts
/**
* Renders a static code block as HTML. Styles from `prism-code-editor/code-block.css`
* are required in addition to the normal layout.
* @param options Options controlling how to render the code block. Any extra properties
* not in {@link CodeBlockOptions} will be stringified as JSON and later parsed by
* {@link forEachCodeBlock}.
* @returns String of HTML for the static code block.
*/
var renderCodeBlock = (options) => {
let { language, value, tabSize, lineNumbers, lineNumberStart = 1, wordWrap, preserveIndent = wordWrap, guideIndents, rtl, class: userClass, tokenizeCallback, addLineClass, ...rest } = options;
tabSize = +tabSize || 2;
let html = `<pre class="prism-code-editor language-${escapeQuotes(language)}${lineNumbers ? " show-line-numbers" : ""} pce-${wordWrap ? "" : "no"}wrap${rtl ? " pce-rtl" : ""}${preserveIndent ? " pce-preserve" : ""}${guideIndents && !rtl ? " pce-guides" : ""}${userClass ? " " + escapeQuotes(userClass) : ""}" data-props='${escapeHtml(JSON.stringify(rest), /'/g, "'")}' `;
let indents = preserveIndent || guideIndents && !rtl ? getIndents(value, tabSize) : null;
if (preserveIndent) value = value.replace(/ /g, " ".repeat(tabSize));
let tokens = tokenizeText(value.includes("\r") ? value.replace(/\r\n?/g, "\n") : value, languages[language] || {});
tokenizeCallback?.(tokens, language);
let lines = highlightTokens(tokens).split("\n");
let l = lines.length;
let i = 0;
html += `style="--tab-size:${tabSize}${lineNumbers ? `;--number-width:${(0 | Math.log10(l + lineNumberStart - 1)) + 1}.001ch${lineNumberStart - 1 ? `;counter-reset:line ${lineNumberStart - 1}` : ""}` : ""}"><code class=pce-wrapper><div class=pce-overlays></div>`;
while (i < l) {
let lineClass = addLineClass?.(i + 1);
html += `<div class="pce-line${lineClass ? " " + escapeQuotes(lineClass) : ""}"${indents?.[i] ? ` style=--indent:${indents[i]}ch` : ""}>${lines[i++]}\n</div>`;
}
return html + "</code></pre>";
};
var getIndents = (code, tabSize) => {
const lines = code.split("\n");
const l = lines.length;
const result = Array(l).fill(0);
for (let prevIndent = 0, emptyPos = -1, i = 0; i < l; i++) {
let line = lines[i];
let l = line.search(/\S/);
let indent = 0;
if (l < 0) {
if (emptyPos < 0) emptyPos = i;
} else {
for (let i = 0; i < l;) indent += line[i++] == " " ? tabSize - indent % tabSize : 1;
if (emptyPos + 1) {
if (indent != prevIndent) prevIndent = Math.min(indent, prevIndent) + 1;
while (emptyPos < i) result[emptyPos++] = prevIndent;
}
result[i] = prevIndent = indent;
emptyPos = -1;
}
}
return result;
};
//#endregion
//#region src/ssr/guides.ts
/**
* Function that renders indentation guides for an editor as an HTML string. Intended to
* be used as an overlay with {@link renderEditor}.
* @param options Render options for the editor.
* @returns HTML string for the indentation guides.
*/
var indentGuides = (options) => {
if (!options.wordWrap) {
let html = "<div class=guide-indents> ";
let indents = getIndentGuides(options.value, +options.tabSize || 2);
let active;
let i = 0;
let top;
let indent;
for (; top = indents[i]?.[0], top < 2; i++) if (!top) active = i;
else {
if (indents[i + 1]?.[0] != 1) active = i;
break;
}
for (i = 0; indent = indents[i]; i++) html += `<div style=top:${indent[0]}00%;left:${indent[1]}00%;height:${indent[2]}00%${i == active ? " class=active-indent" : ""}></div>`;
return html + "</div>";
}
};
//#endregion
//#region src/ssr/index.ts
/**
* This function renders an editor as an HTML string. This is intended to the used with
* server-side rendering (SSR) or static-site generation (SSG). The editor can the later
* be made interactive on the client with the {@link mountEditorsUnder} function.
*
* @param options Options used for the editor. Any properties you define are stringified
* to JSON, which will later be parsed by {@link mountEditorsUnder}. This is very useful
* if you want to add extra configuration options used to customize how the editor is
* mounted.
*/
var renderEditor = (options) => {
let { language, value, lineNumbers, wordWrap, rtl, readOnly, tabSize, tokenizeCallback, overlays, class: userClass, ...rest } = options;
let containerClass = `prism-code-editor language-${language}${lineNumbers == false ? "" : " show-line-numbers"} pce-${wordWrap ? "" : "no"}wrap${rtl ? " pce-rtl" : ""} pce-no-selection${readOnly ? " pce-readonly" : ""}`;
let html = `<div class="${escapeQuotes(containerClass + (userClass ? " " + userClass : ""))}"${userClass ? ` data-start=${containerClass.length + 1}` : ""} data-options='${escapeHtml(JSON.stringify(rest), /'/g, "'")}' `;
let tokens = tokenizeText(value.includes("\r") ? value.replace(/\r\n?/g, "\n") : value, languages[language] || {});
tokenizeCallback?.(tokens, language);
let lines = highlightTokens(tokens).split("\n");
let l = lines.length;
let i = 0;
html += `style=tab-size:${+tabSize || 2};--number-width:${(0 | Math.log10(l)) + 1}.001ch><div class=pce-wrapper><div class=pce-overlays><textarea class=pce-textarea spellcheck=false autocapitalize=off autocomplete=off></textarea>`;
overlays?.forEach((overlay) => {
html += overlay(options) || "";
});
html += "</div>";
while (i < l) html += `<div class=${i ? "pce-line" : "\"pce-line active-line\""} aria-hidden=true>${lines[i++]}\n</div>`;
return html + "</div></div>";
};
//#endregion
export { indentGuides, rainbowBrackets, renderCodeBlock, renderEditor };
//# sourceMappingURL=index.js.map