mirror of
https://github.com/imezx/Warp.git
synced 2025-04-24 15:10:03 +00:00
394 lines
11 KiB
JavaScript
394 lines
11 KiB
JavaScript
import { addClassToHast } from 'shikiji';
|
|
import { addClassToHast as addClassToHast$1 } from 'shikiji/core';
|
|
|
|
function separateContinuousSpaces(inputs) {
|
|
const result = [];
|
|
let current = "";
|
|
function bump() {
|
|
if (current.length)
|
|
result.push(current);
|
|
current = "";
|
|
}
|
|
inputs.forEach((part, idx) => {
|
|
if (isTab(part)) {
|
|
bump();
|
|
result.push(part);
|
|
} else if (isSpace(part) && (isSpace(inputs[idx - 1]) || isSpace(inputs[idx + 1]))) {
|
|
bump();
|
|
result.push(part);
|
|
} else {
|
|
current += part;
|
|
}
|
|
});
|
|
bump();
|
|
return result;
|
|
}
|
|
function isTab(part) {
|
|
return part === " ";
|
|
}
|
|
function isSpace(part) {
|
|
return part === " " || part === " ";
|
|
}
|
|
function splitSpaces(parts, type, renderContinuousSpaces = true) {
|
|
if (type === "all")
|
|
return parts;
|
|
let leftCount = 0;
|
|
let rightCount = 0;
|
|
if (type === "boundary") {
|
|
for (let i = 0; i < parts.length; i++) {
|
|
if (isSpace(parts[i]))
|
|
leftCount++;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (type === "boundary" || type === "trailing") {
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
if (isSpace(parts[i]))
|
|
rightCount++;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
const middle = parts.slice(leftCount, parts.length - rightCount);
|
|
return [
|
|
...parts.slice(0, leftCount),
|
|
...renderContinuousSpaces ? separateContinuousSpaces(middle) : [middle.join("")],
|
|
...parts.slice(parts.length - rightCount)
|
|
];
|
|
}
|
|
|
|
function transformerRenderWhitespace(options = {}) {
|
|
const classMap = {
|
|
" ": options.classSpace ?? "space",
|
|
" ": options.classTab ?? "tab"
|
|
};
|
|
const position = options.position ?? "all";
|
|
const keys = Object.keys(classMap);
|
|
return {
|
|
name: "shikiji-transformers:render-whitespace",
|
|
// We use `root` hook here to ensure it runs after all other transformers
|
|
root(root) {
|
|
const pre = root.children[0];
|
|
const code = pre.children[0];
|
|
code.children.forEach(
|
|
(line) => {
|
|
if (line.type !== "element")
|
|
return;
|
|
const elements = line.children.filter((token) => token.type === "element");
|
|
const last = elements.length - 1;
|
|
line.children = line.children.flatMap((token) => {
|
|
if (token.type !== "element")
|
|
return token;
|
|
const index = elements.indexOf(token);
|
|
if (position === "boundary" && index !== 0 && index !== last)
|
|
return token;
|
|
if (position === "trailing" && index !== last)
|
|
return token;
|
|
const node = token.children[0];
|
|
if (node.type !== "text" || !node.value)
|
|
return token;
|
|
const parts = splitSpaces(
|
|
node.value.split(/([ \t])/).filter((i) => i.length),
|
|
position === "boundary" && index === last && last !== 0 ? "trailing" : position,
|
|
position !== "trailing"
|
|
);
|
|
if (parts.length <= 1)
|
|
return token;
|
|
return parts.map((part) => {
|
|
const clone = {
|
|
...token,
|
|
properties: { ...token.properties }
|
|
};
|
|
clone.children = [{ type: "text", value: part }];
|
|
if (keys.includes(part)) {
|
|
addClassToHast(clone, classMap[part]);
|
|
delete clone.properties.style;
|
|
}
|
|
return clone;
|
|
});
|
|
});
|
|
}
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
function transformerRemoveLineBreak() {
|
|
return {
|
|
name: "shikiji-transformers:remove-line-break",
|
|
code(code) {
|
|
code.children = code.children.filter((line) => !(line.type === "text" && line.value === "\n"));
|
|
}
|
|
};
|
|
}
|
|
|
|
function transformerCompactLineOptions(lineOptions = []) {
|
|
return {
|
|
name: "shikiji-transformers:compact-line-options",
|
|
line(node, line) {
|
|
const lineOption = lineOptions.find((o) => o.line === line);
|
|
if (lineOption?.classes)
|
|
addClassToHast(node, lineOption.classes);
|
|
return node;
|
|
}
|
|
};
|
|
}
|
|
|
|
function createCommentNotationTransformer(name, regex, onMatch, removeEmptyLines = false) {
|
|
return {
|
|
name,
|
|
code(code) {
|
|
const lines = code.children.filter((i) => i.type === "element");
|
|
const linesToRemove = [];
|
|
lines.forEach((line, idx) => {
|
|
let nodeToRemove;
|
|
for (const child of line.children) {
|
|
if (child.type !== "element")
|
|
continue;
|
|
const text = child.children[0];
|
|
if (text.type !== "text")
|
|
continue;
|
|
let replaced = false;
|
|
text.value = text.value.replace(regex, (...match) => {
|
|
if (onMatch.call(this, match, line, child, lines, idx)) {
|
|
replaced = true;
|
|
return "";
|
|
}
|
|
return match[0];
|
|
});
|
|
if (replaced && !text.value.trim())
|
|
nodeToRemove = child;
|
|
}
|
|
if (nodeToRemove) {
|
|
line.children.splice(line.children.indexOf(nodeToRemove), 1);
|
|
if (line.children.length === 0) {
|
|
linesToRemove.push(line);
|
|
if (removeEmptyLines) {
|
|
const next = code.children[code.children.indexOf(line) + 1];
|
|
if (next && next.type === "text" && next.value === "\n")
|
|
linesToRemove.push(next);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
for (const line of linesToRemove)
|
|
code.children.splice(code.children.indexOf(line), 1);
|
|
}
|
|
};
|
|
}
|
|
|
|
function escapeRegExp(str) {
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
function transformerNotationMap(options = {}, name = "shikiji-transformers:notation-map") {
|
|
const {
|
|
classMap = {},
|
|
classActivePre = void 0
|
|
} = options;
|
|
return createCommentNotationTransformer(
|
|
name,
|
|
new RegExp(`\\s*(?://|/\\*|<!--|#)\\s+\\[!code (${Object.keys(classMap).map(escapeRegExp).join("|")})(:\\d+)?\\]\\s*(?:\\*/|-->)?`),
|
|
function([_, match, range = ":1"], _line, _comment, lines, index) {
|
|
const lineNum = Number.parseInt(range.slice(1), 10);
|
|
lines.slice(index, index + lineNum).forEach((line) => {
|
|
addClassToHast(line, classMap[match]);
|
|
});
|
|
if (classActivePre)
|
|
addClassToHast(this.pre, classActivePre);
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
function transformerNotationFocus(options = {}) {
|
|
const {
|
|
classActiveLine = "focused",
|
|
classActivePre = "has-focused"
|
|
} = options;
|
|
return transformerNotationMap(
|
|
{
|
|
classMap: {
|
|
focus: classActiveLine
|
|
},
|
|
classActivePre
|
|
},
|
|
"shikiji-transformers:notation-focus"
|
|
);
|
|
}
|
|
|
|
function transformerNotationHighlight(options = {}) {
|
|
const {
|
|
classActiveLine = "highlighted",
|
|
classActivePre = "has-highlighted"
|
|
} = options;
|
|
return transformerNotationMap(
|
|
{
|
|
classMap: {
|
|
highlight: classActiveLine,
|
|
hl: classActiveLine
|
|
},
|
|
classActivePre
|
|
},
|
|
"shikiji-transformers:notation-highlight"
|
|
);
|
|
}
|
|
|
|
function highlightWordInLine(line, ignoredElement, word, className) {
|
|
line.children = line.children.flatMap((span) => {
|
|
if (span.type !== "element" || span.tagName !== "span" || span === ignoredElement)
|
|
return span;
|
|
const textNode = span.children[0];
|
|
if (textNode.type !== "text")
|
|
return span;
|
|
return replaceSpan(span, textNode.value, word, className) ?? span;
|
|
});
|
|
}
|
|
function inheritElement(original, overrides) {
|
|
return {
|
|
...original,
|
|
properties: {
|
|
...original.properties
|
|
},
|
|
...overrides
|
|
};
|
|
}
|
|
function replaceSpan(span, text, word, className) {
|
|
const index = text.indexOf(word);
|
|
if (index === -1)
|
|
return;
|
|
const createNode = (value) => inheritElement(span, {
|
|
children: [
|
|
{
|
|
type: "text",
|
|
value
|
|
}
|
|
]
|
|
});
|
|
const nodes = [];
|
|
if (index > 0)
|
|
nodes.push(createNode(text.slice(0, index)));
|
|
const highlightedNode = createNode(word);
|
|
addClassToHast$1(highlightedNode, className);
|
|
nodes.push(highlightedNode);
|
|
if (index + word.length < text.length)
|
|
nodes.push(createNode(text.slice(index + word.length)));
|
|
return nodes;
|
|
}
|
|
|
|
function transformerNotationWordHighlight(options = {}) {
|
|
const {
|
|
classActiveWord = "highlighted-word",
|
|
classActivePre = void 0
|
|
} = options;
|
|
return createCommentNotationTransformer(
|
|
"shikiji-transformers:notation-highlight-word",
|
|
// comment-start | marker | word | range | comment-end
|
|
/^\s*(?:\/\/|\/\*|<!--|#)\s+\[!code word:((?:\\.|[^:\]])+)(:\d+)?\]\s*(?:\*\/|-->)?/,
|
|
function([_, word, range], _line, comment, lines, index) {
|
|
const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
|
|
word = word.replace(/\\(.)/g, "$1");
|
|
lines.slice(index + 1, index + 1 + lineNum).forEach((line) => highlightWordInLine(line, comment, word, classActiveWord));
|
|
if (classActivePre)
|
|
addClassToHast(this.pre, classActivePre);
|
|
return true;
|
|
},
|
|
true
|
|
// remove empty lines
|
|
);
|
|
}
|
|
|
|
function parseMetaHighlightWords(meta) {
|
|
if (!meta)
|
|
return [];
|
|
const match = Array.from(meta.matchAll(/\/((?:\\.|[^\/])+?)\//ig));
|
|
return match.map((v) => v[1].replace(/\\(.)/g, "$1"));
|
|
}
|
|
function transformerMetaWordHighlight(options = {}) {
|
|
const {
|
|
className = "highlighted-word"
|
|
} = options;
|
|
return {
|
|
name: "shikiji-transformers:meta-word-highlight",
|
|
line(node) {
|
|
if (!this.options.meta?.__raw)
|
|
return;
|
|
const words = parseMetaHighlightWords(this.options.meta.__raw);
|
|
for (const word of words)
|
|
highlightWordInLine(node, null, word, className);
|
|
return node;
|
|
}
|
|
};
|
|
}
|
|
|
|
function transformerNotationDiff(options = {}) {
|
|
const {
|
|
classLineAdd = "diff add",
|
|
classLineRemove = "diff remove",
|
|
classActivePre = "has-diff"
|
|
} = options;
|
|
return transformerNotationMap(
|
|
{
|
|
classMap: {
|
|
"++": classLineAdd,
|
|
"--": classLineRemove
|
|
},
|
|
classActivePre
|
|
},
|
|
"shikiji-transformers:notation-diff"
|
|
);
|
|
}
|
|
|
|
function transformerNotationErrorLevel(options = {}) {
|
|
const {
|
|
classMap = {
|
|
error: ["highlighted", "error"],
|
|
warning: ["highlighted", "warning"]
|
|
},
|
|
classActivePre = "has-highlighted"
|
|
} = options;
|
|
return transformerNotationMap(
|
|
{
|
|
classMap,
|
|
classActivePre
|
|
},
|
|
"shikiji-transformers:notation-error-level"
|
|
);
|
|
}
|
|
|
|
function parseMetaHighlightString(meta) {
|
|
if (!meta)
|
|
return null;
|
|
const match = meta.match(/{([\d,-]+)}/);
|
|
if (!match)
|
|
return null;
|
|
const lines = match[1].split(",").flatMap((v) => {
|
|
const num = v.split("-").map((v2) => Number.parseInt(v2, 10));
|
|
if (num.length === 1)
|
|
return [num[0]];
|
|
else
|
|
return Array.from({ length: num[1] - num[0] + 1 }, (_, i) => i + num[0]);
|
|
});
|
|
return lines;
|
|
}
|
|
const symbol = Symbol("highlighted-lines");
|
|
function transformerMetaHighlight(options = {}) {
|
|
const {
|
|
className = "highlighted"
|
|
} = options;
|
|
return {
|
|
name: "shikiji-transformers:meta-highlight",
|
|
line(node, line) {
|
|
var _a;
|
|
if (!this.options.meta?.__raw)
|
|
return;
|
|
(_a = this.meta)[symbol] || (_a[symbol] = parseMetaHighlightString(this.options.meta.__raw));
|
|
const lines = this.meta[symbol] || [];
|
|
if (lines.includes(line))
|
|
addClassToHast(node, className);
|
|
return node;
|
|
}
|
|
};
|
|
}
|
|
|
|
export { createCommentNotationTransformer, parseMetaHighlightString, parseMetaHighlightWords, transformerCompactLineOptions, transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRemoveLineBreak, transformerRenderWhitespace };
|