prism-react-editor
Version:
Lightweight, extensible code editor component for React apps
265 lines (264 loc) • 8.79 kB
JavaScript
"use client";
import { useRef, useLayoutEffect } from "react";
import { u as useStableRef, n as numLines, l as languageMap, a as addListener } from "../../core-Dm5I6BkG.js";
import { k as createTemplate, u as updateNode, h as getLineBefore, f as getLineEnd } from "../../local-Cq-4Fajb.js";
const template = /* @__PURE__ */ createTemplate("<div class=pce-fold><div> ");
const template2 = /* @__PURE__ */ createTemplate(
"<div class=pce-unfold> <span title=Unfold> </span> "
);
const isMultiline = (str, start, end) => str.slice(start, end).includes("\n");
const useReadOnlyCodeFolding = (editor, ...providers) => {
let value;
let code;
let lines;
let lineNumberWidth;
let foldPositions;
const foldToggles = [];
const foldPlaceholders = [];
const foldedLines = /* @__PURE__ */ new Set();
const foldedRanges = /* @__PURE__ */ new Set();
const providerRef = useRef(providers);
const getPosition = (pos) => {
let result = pos;
for (let [start, end] of foldedRanges) {
if (pos > start) {
if (pos < end) return -1;
result -= end - start - 3;
}
}
return result;
};
const toggleFold = (line) => {
const start = foldPositions[line][0];
const addFold = (line2) => {
let [start2, end] = foldPositions[line2];
let expanded;
for (let range of foldedRanges) {
if (start2 <= range[0] && end > range[0]) {
if (expanded) foldedRanges.delete(range);
else {
range[0] = start2;
if (end > range[1]) range[1] = end;
expanded = true;
}
}
}
if (!expanded) foldedRanges.add([start2, end]);
};
if (foldedLines.has(line)) {
foldedLines.delete(line);
for (let range of foldedRanges) {
if (start == range[0]) {
foldedRanges.delete(range);
for (let currentLine of foldedLines) {
const pos = foldPositions[currentLine][0];
if (pos > start) addFold(currentLine);
}
break;
}
}
} else {
foldedLines.add(line);
addFold(line);
}
};
const update = (line) => {
value = "";
let pos = 0;
let ln = 1;
let skippedLines = [];
let sortedRanges = [...foldedRanges].sort((a, b) => a[0] - b[0]);
let textarea = editor.textarea;
for (let [start, end] of sortedRanges) {
value += code.slice(pos, start) + " ";
skippedLines[ln += numLines(code, pos, start) - 1] = numLines(code, start, pos = end);
}
textarea.value = value += code.slice(pos);
if (line) textarea.setSelectionRange(pos = getPosition(foldPositions[line][0]), pos);
editor.update();
for (let i = 1, j = 0, l = lines.length; i < l; i++)
lines[i].setAttribute("data-line", j += skippedLines[i - 1] || 1);
editor.container.style.setProperty("--number-width", lineNumberWidth);
updateFolds();
};
const updateFolds = () => {
for (let line = 0, l = foldPositions.length, prev; line < l; line++) {
if (!foldPositions[line]) continue;
let pos = getPosition(foldPositions[line][0]);
if (pos + 1) {
let parent = lines[numLines(value, 0, pos)];
let el = foldToggles[line];
let isClosed = foldedLines.has(line);
let pos2 = getPosition(foldPositions[line][1]);
if (!el) {
el = foldToggles[line] = template();
addListener(el, "click", () => toggleAndUpdate(line));
}
if (parent != el.parentNode && parent != prev) parent.prepend(el);
prev = parent;
el.classList.toggle("closed-fold", isClosed);
el.title = `${isClosed ? "Unf" : "F"}old line`;
el = foldPlaceholders[line];
if (isClosed) {
if (!el) {
el = foldPlaceholders[line] = template2();
addListener(el, "click", () => toggleAndUpdate(line));
}
updateNode(el.firstChild, getLineBefore(value, pos));
updateNode(el.lastChild, value.slice(pos2, getLineEnd(value, pos2)));
if (parent != el.parentNode) parent.prepend(el);
} else el?.remove();
}
}
};
const toggleAndUpdate = (line) => {
toggleFold(line);
update(line);
};
const createFolds = useStableRef(() => {
foldPositions = [];
foldedRanges.clear();
foldedLines.clear();
lines = editor.lines;
value = code = editor.value;
lineNumberWidth = (0 | Math.log10(numLines(code))) + 1 + ".001ch";
const folds = [];
providerRef.current.forEach((clb) => folds.push(...clb(editor, folds) || []));
for (let i = 0, l = folds.length; i < l; i++) {
const [start, end] = folds[i], index = numLines(value, 0, start);
if (!foldPositions[index] || end > foldPositions[index][1])
foldPositions[index] = [start, end];
}
updateFolds();
});
providerRef.current = providers;
useLayoutEffect(
useStableRef(() => {
editor.extensions.folding = {
get fullCode() {
return code;
},
toggleFold: (lineNumber, force) => !!foldPositions[lineNumber] && foldedLines.has(lineNumber) != force && !toggleFold(lineNumber),
updateFolds: () => update()
};
if (editor.value) createFolds();
return () => {
delete editor.extensions.folding;
if (foldToggles) {
foldToggles.forEach((el, i) => {
el.remove();
foldPlaceholders[i]?.remove();
});
}
if (foldedRanges.size) {
foldedRanges.clear();
foldPositions = [];
update();
}
};
}),
[]
);
useLayoutEffect(() => {
setTimeout(editor.on("update", createFolds));
});
};
const bracketFolding = ({ value, extensions: { matchBrackets } }) => {
if (matchBrackets) {
let folds = [];
let { brackets, pairs } = matchBrackets;
let i = 0;
let j;
let l = pairs.length;
for (; i < l; i++) {
if ((j = pairs[i]) > i && isMultiline(value, brackets[i][1], brackets[j][1])) {
folds.push([brackets[i][2], brackets[j][1]]);
}
}
return folds;
}
};
const tagFolding = ({ value, extensions: { matchTags } }) => {
if (matchTags) {
let folds = [];
let { tags, pairs } = matchTags;
let i = 0;
let j;
let l = pairs.length;
for (; i < l; i++) {
if ((j = pairs[i]) > i && isMultiline(value, tags[i][2], tags[j][1])) {
folds.push([tags[i][2], tags[j][1]]);
}
}
return folds;
}
};
const blockCommentFolding = ({ tokens, value, props: { language } }) => {
const folds = [];
const findBlockComments = (tokens2, position, language2) => {
for (let i = 0, l = tokens2.length; i < l; ) {
const token = tokens2[i++];
const content = token.content;
const length = token.length;
const aliasType = token.alias || token.type;
if (aliasType == "comment" && isMultiline(value, position, position + length)) {
let comment = languageMap[language2]?.comments?.block;
if (comment && value.indexOf(comment[0], position) == position)
folds.push([position + comment[0].length, position + length - comment[1].length]);
} else if (Array.isArray(content)) {
findBlockComments(
content,
position,
aliasType.slice(0, 9) == "language-" ? aliasType.slice(9) : language2
);
}
position += length;
}
};
findBlockComments(tokens, 0, language);
return folds;
};
const markdownFolding = ({ tokens, value, props: { language } }) => {
if (language == "markdown" || language == "md") {
let folds = [];
let pos = 0;
let openTitles = [];
let levels;
let closeTitles = (level) => {
for (let end = value.slice(0, pos).trimEnd().length; level <= levels; ) {
folds.push([openTitles[level++], end]);
}
};
let i = 0;
let l = tokens.length;
for (; i < l; ) {
const token = tokens[i++];
const length = token.length;
const type = token.type;
if (type == "code" && !token.alias) {
let content = token.content;
folds.push([
pos + content[0].length + (content[1].content || "").length,
pos + length - content[content.length - 1].length - 1
]);
}
if (type == "title") {
let [token1, token2] = token.content;
let level = token1.type ? token1.length - 1 : token2.content[0] == "=" ? 0 : 1;
closeTitles(level);
openTitles[levels = level] = pos + (token1.type ? length : token1.length - 1);
}
pos += length;
}
closeTitles(0);
return folds;
}
};
export {
blockCommentFolding,
bracketFolding,
markdownFolding,
tagFolding,
useReadOnlyCodeFolding
};
//# sourceMappingURL=index.js.map