prism-react-editor
Version:
Lightweight, extensible code editor component for React apps
155 lines (154 loc) • 5.16 kB
JavaScript
"use client";
import { useLayoutEffect } from "react";
import { n as getClosestToken, a as addTextareaListener, v as voidlessLangs, o as voidTags } from "../local-Cq-4Fajb.js";
const useTagMatcher = (editor) => {
useLayoutEffect(() => {
let code;
let tagIndex;
let sp;
const stack = [];
const pairMap = [];
const tags = [];
const matchTags = (tokens, language, value) => {
code = value;
tags.length = pairMap.length = sp = tagIndex = 0;
matchTagsRecursive(tokens, language, 0);
};
const matchTagsRecursive = (tokens, language, position) => {
let noVoidTags = voidlessLangs.has(language);
let i = 0;
let l = tokens.length;
for (; i < l; ) {
const token = tokens[i++];
const content = token.content;
const length = token.length;
if (Array.isArray(content)) {
if (token.type == "tag" && code[position] == "<") {
const openLen = content[0].length;
const tagName = content[2] ? code.substr(position + openLen, content[1].length) : "";
const notSelfClosing = content[content.length - 1].length < 2 && (noVoidTags || !voidTags.test(tagName));
if (content[2] && noVoidTags) matchTagsRecursive(content, language, position);
if (notSelfClosing) {
if (openLen > 1) {
for (let i2 = sp; i2; ) {
if (tagName == stack[--i2][1]) {
pairMap[pairMap[tagIndex] = stack[sp = i2][0]] = tagIndex;
i2 = 0;
}
}
} else {
stack[sp++] = [tagIndex, tagName];
}
}
tags[tagIndex++] = [
token,
position,
position + length,
tagName,
openLen > 1,
notSelfClosing
];
} else {
let lang = token.alias || token.type;
matchTagsRecursive(
content,
lang.slice(0, 9) == "language-" ? lang.slice(9) : language,
position
);
}
}
position += length;
}
};
const cleanup = editor.on("tokenize", matchTags);
matchTags(editor.tokens, editor.props.language, editor.value);
editor.extensions.matchTags = {
tags,
pairs: pairMap
};
return () => {
delete editor.extensions.matchTags;
cleanup();
};
}, []);
};
const getClosestTagIndex = (pos, tags) => {
for (let i = 0, l = tags.length; i < l; i++) if (tags[i][1] <= pos && tags[i][2] >= pos) return i;
};
const useHighlightMatchingTags = (editor) => {
useLayoutEffect(() => {
let openEl, closeEl;
const highlight = (remove) => [openEl, closeEl].forEach((el) => {
el && el.classList.toggle("active-tagname", !remove);
});
const cleanup = editor.on("selectionChange", ([start, end]) => {
let newEl1;
let newEl2;
let index;
let matcher = editor.extensions.matchTags;
if (start == end && matcher && editor.focused) {
index = getClosestTagIndex(start, matcher.tags);
if (index + 1) {
index = matcher.pairs[index];
if (index + 1 && (newEl1 = getClosestToken(editor, ".tag>.tag"))) {
newEl2 = getClosestToken(editor, ".tag>.tag", 2, 0, matcher.tags[index][1]);
}
}
}
if (openEl != newEl1) {
highlight(true);
openEl = newEl1;
closeEl = newEl2;
highlight();
}
});
return () => {
cleanup();
highlight(true);
};
}, []);
};
const useHighlightTagPunctuation = (editor, className, alwaysHighlight) => {
useLayoutEffect(() => {
let openEl, closeEl;
let getPunctuation = (pos) => getClosestToken(editor, ".tag>.punctuation", 0, 0, pos);
let highlight = (remove) => [openEl, closeEl].forEach((el) => {
el && el.classList.toggle(className, !remove);
});
let selectionChange = () => {
let [start, end] = editor.getSelection();
let matcher = editor.extensions.matchTags;
let newEl1;
let newEl2;
if (editor.focused && matcher && start == end) {
let tag = matcher.tags[getClosestTagIndex(start, matcher.tags)];
if (tag && (alwaysHighlight || !getClosestToken(editor, ".tag>.tag") && getPunctuation())) {
newEl1 = getPunctuation(tag[1]);
newEl2 = getPunctuation(tag[2] - 1);
}
}
if (openEl != newEl1 || closeEl != newEl2) {
highlight(true);
openEl = newEl1;
closeEl = newEl2;
highlight();
}
};
let cleanup1 = editor.on("selectionChange", selectionChange);
let cleanup2 = addTextareaListener(editor, "focus", selectionChange);
let cleanup3 = addTextareaListener(editor, "blur", selectionChange);
selectionChange();
return () => {
cleanup1();
cleanup2();
cleanup3();
highlight(true);
};
}, [className, alwaysHighlight]);
};
export {
useHighlightMatchingTags,
useHighlightTagPunctuation,
useTagMatcher
};
//# sourceMappingURL=match-tags.js.map