simple-syntax-highlighter
Version:
No chi-chi syntax highlighter for Vue.js 3 (and 2).
318 lines (317 loc) • 18.2 kB
JavaScript
import { ref as S, useSlots as _, computed as j, openBlock as $, createElementBlock as C, normalizeClass as G, renderSlot as W, createTextVNode as R, createCommentVNode as V, createElementVNode as K } from "vue";
const Y = ["data-type", "data-label"], X = ["contenteditable", "innerHTML"], J = {
__name: "simple-syntax-highlighter",
props: {
language: { type: String, default: "" },
label: { type: [String, Boolean], default: !1 },
// The characters that should be inserted on tab key press.
tab: { type: [Boolean, String], default: " " },
dark: { type: Boolean, default: !1 },
copyButton: { type: Boolean, default: !1 },
editable: { type: Boolean, default: !1 }
},
emits: ["copied"],
setup(m, { emit: A }) {
const o = {
quote: /("(?:\\"|[^"])*")|('(?:\\'|[^'])*')/,
// Match simple and double quotes by pair.
comment: /(\/\/.*?(?:\n|$)|\/\*.*?(?:\*\/|$))/,
// Trailing (// ...) or blocks (/* ... */) comments.
doctype: /(<!DOCTYPE.*?>)/,
// Doctype is case insensitive.
// A tag captures everything between < and >, and handles: <tag>, or <tag/>, or </tag>.
// The part `(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)` makes sure there can be & inside
// quoted attribute value (`attr="& < >"` or `attr='& < >'`).
htmlTag: /<(?:([a-z][\w:-]*)((?:[\w\- ]+=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))>/,
htmlentity: /(&(?:[a-z]+|#x?\d+);)/,
// Punctuation outside of html tags, quotes and comments.
punctuation: new RegExp("(!==?|(?:[[\\](){}.:,+\\-?=!])+|(?<!&(?:[a-z]+|#x?\\d+));|\\|\\||<|>|&)"),
number: /(-?(?:\.\d+|\d+(?:\.\d+)?))/,
boolean: /\b(true|false)\b/
}, h = {
shell: {
quote: o.quote,
comment: /(#.*?)(?:\n|$)/,
keyword: /\b(p?npm|yarn|i(?:nstall)?|run|audit|outdated|update|publish|whoami|cd|sudo|chmod|chown|ls|cat|vim?|scp|rm|mv|mkdir|ln|open|cwd|which|clear|curl|ping|systemctl|echo|export|open|bash|exit)\b/,
param: /( --(?:save|save-dev))(?:\s|$)/
},
xml: {
doctype: o.doctype,
quote: o.quote,
comment: /(<!--.*?(?:-->|$))/,
htmlentity: o.htmlentity,
tag: o.htmlTag
},
html: {
doctype: o.doctype,
quote: o.quote,
comment: /(<!--.*?(?:-->|$))/,
htmlentity: o.htmlentity,
tag: o.htmlTag
},
"html-vue": {
doctype: o.doctype,
quote: o.quote,
comment: /(<!--.*?(?:-->|$))/,
htmlentity: o.htmlentity,
tag: /<(?:([a-z][\w:-]*)((?:[\w-]+\s*=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))>/
},
// @todo: support Pug inline tags like `#[em italic]`.
pug: {
// Text match for this syntax:
// tag
// | text
text: /((?:^|\n)[ \t]*|^)\|([ \t]*)([^\n]+(?=\s*(?:\n|$)))/,
// Text match for this syntax:
// tag.
// text
// See caveat #3 (backreferences).
text2: /([ \t]*)([.#\w-]+(?:\([^)]*\))*)\.\n((?:\n+(?=\4[ \t]+)|(?=\4[ \t]+).+?(?:\n|$)*?)*)(?=\s*(?:\n|$))/,
// text2: /^([ \t]*)([.#\w-]+(?:\([^)]*\))*)\.\n((?:(?:^\4[ \t]+)(?:[^\n]*)\n)*)/,
quote: o.quote,
// See caveat #3 (backreferences).
comment: /(^|\n)([ \t]*|^)(\/\/-[ \t]*(?:[^\n]*?(?:\n\10[ \t]+[^\n]*)+|[^\n]+(?=\n|$)))/,
// A tag captures everything like `tag`, `.tag(attrs)`, `#tag(attrs)`, `div.tag(attrs)`.
// 6 groups: 1. tag, 2. class & id, 3. attrs, 4. dot or not, 5. indent before content, 6. inner html.
// The part `(?:[@:]?[a-z][\w-]+(?:=(?:"[^"]*"|'[^']*'))*\s*)*` matches all the attributes together.
// It's a repeated group (0+) of attribute key-value pairs or key only. If there is an attribute
// value, it makes sure the attribute is formed with matching quotes or double quotes and any character
// inside.
tag: /([a-z][\w:-]*|)([.#][a-z][.\w-]*|)\b(?:\((\s*(?:[#@:]?[a-z][\w-.:]+(?:=(?:"[^"]*"|'[^']*'))*\s*)*)\))?(\.?)([ \t]*)([^\n]+)?(?=\n|$)/,
"inline-tag": /#\[([^[\]]+)\]/
// Only performed inside tags inner texts.
// htmlentity: regexBasics.htmlentity // Only performed inside tags inner texts.
},
css: {
comment: /(\/\*.+?(?:\*\/|$))/,
// Blocks comments (/* ... */).
quote: /("(?:\\"|[^"])*")|('(?:\\'|[^'])*')/,
pseudo: /(:(?:hover|active|focus|visited|not|before|after|(?:first|last|nth)-child))/,
"selector keyword vendor": /(@-(?:moz|o|webkit|ms)-(?=keyframes\s))/,
"selector keyword": /((?:@(?:import|media|font-face|keyframes)|screen|print|and)(?=[\s({])|keyframes|\s(?:ul|ol|li|table|div|pre|p|a|img|br|hr|h[1-6]|em|strong|span|html|body|iframe|video|audio|input|button|form|label|fieldset|small|abbr|i|dd|dt)\b)(?=.*\{})/,
variable: /(--[a-z0-9-]+)/,
// Any part before '{'.
selector: /((?:[.#-\w*+ >:,[\]="~\n]|>)+)(?=\s*\{)/,
// Any part before '{'.
"attribute keyword vendor": /(-(?:moz|o|webkit|ms)-(?=transform|transition|user-select|tap-highlight-color|animation|background-size|box-shadow))/,
"attribute keyword": /\b(content|float|display|position|top|left|right|bottom|(?:(?:max|min)-)?width|(?:(?:max|min|line)-)?height|font(?:-(?:family|style|size|weight|variant|stretch))?|vertical-align|color|opacity|visibility|z-index|pointer-events|user-select|transform(?:-(?:origin|style|delay|duration|property|timing-function))?|transition(?:-(?:delay|duration))?|animation(?:-(?:name|delay|duration|direction|fill-mode))?|backface-visibility|backdrop-filter|background(?:-(?:color|position|image|repeat|size|attachment|origin|clip|blend-mode))?|(?:padding|margin|border)(?:-(?:top|left|right|bottom))?|border(?:-(?:radius|color|width|style|spacing))|white-space|text-(?:align|transform|decoration|shadow|indent)|overflow(?:-(?:x|y))?|(?:letter|word)-spacing|word-break|box-(?:sizing|shadow)|stroke(?:-(?:width|opacity|dasharray|dashoffset|linecap|linejoin))?|fill|speak|outline|user-select|cursor|flex(?:-(?:direction|flow|grow|shrink|basis|wrap))?|(?:justify|align)-(?:content|self|items))(?=\s*:)/,
"value keyword vendor": /(-(?:moz|o|webkit|ms)-(?=linear-gradient))/,
"value keyword": /\b(inherit|initial|normal|none|unset|auto|inline(?:-(?:block|flex))?|block|flex|absolute|relative|static|fixed|sticky|hidden|visible|top|left|right|bottom|center|middle|baseline|solid|dotted|dashed|line-through|(?:over|under)line|wavy|double|(?:pre-|no)?wrap|pre|break-word|(?:upper|lower)case|capitalize|italic|bold|attr\(.*?\)|linear|ease(?:-in)?(?:-out)?|all|infinite|cubic-bezier|(?:translate|rotate)(?:[X-Z]|3d)?|skew[XY]?|scale|(?:no-)?repeat|repeat(?:-x|-y)|contain|cover|url|(?:repeating-)?(?:linear|radial)-gradient|inset|pointer|(?:flex-)?(?:start|end)|space-(?:between|evenly|around)|stretch|revert|row(?:-reverse)?|column(?:-reverse)?)(?=\s*[,;}(]|\s+[\da-z!])/,
"value keyword important": /( ?!important)/,
number: o.number,
color: /(transparent|#(?:[\da-f]{8}|[\da-f]{6}|[\da-f]{3})|rgba?\([\d., ]*\))/,
htmlentity: o.htmlentity,
punctuation: /([:,;{}@#()!]+)/,
attribute: /([a-z-]+)(?=\s*:)/,
unit: /(px|pt|cm|%|r?em|m?s|deg|vh|vw|vmin|vmax)(?=(?:\s*[;,{}})]|\s+[-\da-z#]))/,
error: /([:,;{}@#()!]+|<|>|&)/
},
json: {
quote: /("(?:\\"|[^"])*")/,
// Only match double quotes pairs.
number: o.number,
boolean: o.boolean,
punctuation: /([[\](){}:,]+)/,
// Override default to simplify.
error: /(&(:?lt|gt|amp);|(?!\s).)/
},
js: {
quote: o.quote,
comment: o.comment,
number: /\b(\d+(?:\.\d+)?|null)\b/,
boolean: o.boolean,
this: /\b(this)(?=\W)/,
keyword: /\b(new|getElementsBy(?:Tag|Class|)Name|getElementById|querySelector|querySelectorAll|arguments|if|else|do|return|case|default|(?:f|F)unction|typeof|instanceof|undefined|document(?:Element)?|window|console|while|for|forEach|switch|in|break|continue|delete|length|var|let|const|export|import|as|require|from|Class|constructor|Number|Boolean|String|Array|Object|RegExp|Integer|Date|Promise|Proxy|WeakMap|WeakSet|Symbol|SyncManager|File(?:Reader)?|DataTransfer|DocumentFragment|async|await|(?:clear|set)(?:Timeout|Interval)|parse(?:Int|Float)|Math(?=\.)|isNaN|atob|btoa|getComputedStyle)(?=\W)/,
htmlentity: o.htmlentity,
punctuation: /(!==?|[[\]!(){}:;,+\-%*/?=]+|\.+(?![a-z])|\|\||<|>|&)/,
// Override default since '.' can be part of js variable.
variable: /(\.?[a-z_]\w*)/,
"external-var": /(\$|jQuery|JSON)(?=\W|$)/
// jQuery or $ or JSON.
},
php: {
quote: o.quote,
comment: o.comment,
special: /(<\?(?:php)?|\?>|__(?:DIR|FILE|LINE|CLASS|METHOD|FUNCTION|NAMESPACE|TRAIT)__)/,
punctuation: o.punctuation,
number: o.number,
boolean: o.boolean,
variable: /(\$\w+)/,
keyword: /\b(define|echo|die|exit|print_r|var_dump|if|else|elseif|do|return|case|default|function|\$this|while|foreach|for|switch|in|break|continue|empty|isset|unset|parse_ini_file|session_(?:start|destroy|id)|header|json_(?:encode|decode)|error_log|(require|include)(:?_once)?|try|throw|new|Exception|catch|finally|preg_(?:match|replace)|list|strlen|substr|str_replace|array_(?:keys|values))(?=\W|$)/
},
sql: {
quote: o.quote,
comment: /((?:--|#)\s.*?(?:\n|$)|\/\*.*?\*\/)/,
punctuation: o.punctuation,
number: /\b(\d+(?:\.\d+)?|null)\b/,
boolean: o.boolean,
keyword: /\b(\*|DECLARE|BEGIN|END|RETURNS|FUNCTION|CREATE|DATABASE|TABLE|VIEW|COLUMN|INDEX|GRANT|REVOKE|ALL|PRIVILEGES|IDENTIFIED|FLUSH|ALTER|MODIFY|DROP|TRUNCATE|CONSTRAINT|ADD|CHECK|(?:(?:PRIMARY|FOREIGN|UNIQUE) )?KEY|REFERENCES|AUTO_INCREMENT|COMMENT|DEFAULT|UNSIGNED|CHARSET|COLLATE|CHARACTER|ENGINE|SQL_MODE|USE|IF|THEN|NULL|EXISTS|UNIQUE|SELECT|UPDATE|DELETE|(?:INSERT|REPLACE)(?: INTO)?|VALUES|SET|FROM|WHERE|(?:ORDER|GROUP) BY|LIMIT|(?:(?:LEFT|RIGHT|INNER|OUTER|CROSS) |)JOIN|AS|ON|COUNT|AVG|SUM|MIN|MAX|CASE|TO|WHEN|BETWEEN|AND|OR|NOT|IN|LIKE|IS|CONCAT|SUBSTRING|CURRENT_(?:DATE|TIMESTAMP)|USING|HAVING?)(?=\W|$)/,
"var-type": /\b((?:var)?char|(?:tiny|small|medium|big)?int|decimal|float|double|real|bit|boolean|date(?:time)?|time(?:stamp)?|year|(?:tiny|medium|long)?(?:text|blob)|enum)\b/
}
}, w = {
xml: /(\s*)([a-z\d\-:]+)(?:=("|')(.*?)\3)?/g,
html: /(\s*)([a-z-]+)(?:=("|')(.*?)\3)?/gs,
"html-vue": /(\s*)([@:#]?[a-z\d-]+)(?:(?:=("|')(.*?)\3)|)/gs,
pug: /(\s*|,)([@:#]?[a-z\d-]+)(?:(?:=("|')(.*?)\3)|)/g
}, q = {
shell: { quote: 2 },
xml: { quote: 2, tag: 5 },
html: { quote: 2, tag: 5 },
"html-vue": { quote: 2, tag: 5 },
pug: { text: 3, text2: 3, quote: 2, comment: 3, tag: 6 },
json: {},
php: { quote: 2 },
sql: { quote: 2 },
css: { quote: 2 },
js: { quote: 2 }
}, u = m, L = A, x = S(Object.keys(h)), b = S(null), T = _(), y = (t) => t.map((e) => {
if (!e.children || typeof e.children == "string") return e.children || "";
if (Array.isArray(e.children)) return y(e.children);
if (e.children.default) return y(e.children.default());
}).join(""), f = (t) => t.replace(/[<>]/g, (e) => ({ "<": "<", ">": ">" })[e]), O = (t) => {
if (t === "transparent") return { dark: !1, alpha: 0 };
const e = (s) => s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
let l = 0, c = 0, n = 0, a = 1;
const i = t.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/), r = t.match(/^#([\da-f]{3,8})$/i);
if (i)
l = parseInt(i[1]) / 255, c = parseInt(i[2]) / 255, n = parseInt(i[3]) / 255, i[4] && (a = parseFloat(i[4]));
else if (r) {
const s = r[1], d = s.length === 3 || s.length === 4;
l = parseInt(d ? s[0] + s[0] : s.substring(0, 2), 16) / 255, c = parseInt(d ? s[1] + s[1] : s.substring(2, 4), 16) / 255, n = parseInt(d ? s[2] + s[2] : s.substring(4, 6), 16) / 255, (s.length === 4 || s.length === 8) && (a = parseInt(d ? s[3] + s[3] : s.substring(6, 8), 16) / 255);
}
const g = 0.2126 * e(l) + 0.7152 * e(c) + 0.0722 * e(n);
return { dark: a * g + (1 - a) * 1 <= 0.45, alpha: a };
}, z = () => {
let t = "";
const e = [];
for (const l in h[u.language]) {
const c = q[u.language][l] || 1;
for (let n = 0; n < c; n++) e.push(l);
t += (t ? "|" : "") + h[u.language][l].source;
}
return [t, e];
}, N = (t) => {
const e = (l, c, n, a, i) => (
// `attribute-name`
`${c}<span class="attribute">${n}</span>` + // `=`
(a || i ? '<span class="punctuation">=</span>' : "") + // `"attribute value"`
(a || i ? `<span class="quote">${a || ""}${i || ""}${a || ""}</span>` : "")
);
if (u.language === "pug") {
let [l, c = "", n = "", a = "", i = "", r = ""] = t;
return c = c.replace(/#[\w-]+/g, (g) => `<span class="id">${g}</span>`).replace(/\.[\w-]+/g, (g) => `<span class="class">${g}</span>`), n && (n = n.replace(w.pug, e), n = '<span class="punctuation">(</span>' + n + '<span class="punctuation">)</span>'), r && (r = v(r)), // The tag-name + attributes list if any.
`<span class="tag-name">${l}</span>${c}${n}` + (a ? '<span class="punctuation">.</span>' : "") + (i || "") + (r ? `<span class="text">${r}</span>` : "");
} else {
const [l, c = "", n = "", a = "", i] = t, r = c.replace(w[u.language], e);
return (
// The tag opening: `</` or `<`.
`<span class="punctuation"><${a}</span><span class="tag-name">${l || i}</span>` + r + // The tag end `>` or `/>`.
`<span class="punctuation">${n}></span>`
);
}
}, v = (t) => t.replace(new RegExp(h.pug["inline-tag"], "gsi"), (e, l) => '<span class="inline-tag">#[</span>' + l.replace(new RegExp(h.pug.tag, "si"), (c, ...n) => (n = n.slice(0, n.length - 2), N(n))) + '<span class="inline-tag">]</span>'), M = () => {
if (x.value.includes(u.language)) {
const t = D();
b.value.innerHTML = E(b.value.innerText), I(b.value.childNodes, t);
}
}, D = () => {
const t = window.getSelection();
t.collapseToEnd();
const e = new Range();
return e.setStart(b.value, 0), e.setEnd(t.extentNode, t.extentOffset), e.toString().length;
}, I = (t, e) => {
var c, n;
let l = 0;
for (const a of t) {
const i = ((c = a.innerText) == null ? void 0 : c.length) || a.length;
if (l + i >= e) {
a.childNodes.length > 1 ? I(a.childNodes, e - l) : document.getSelection().setPosition(((n = a.childNodes) == null ? void 0 : n[0]) || a, e - l);
break;
}
l += i;
}
}, U = (t) => {
switch (t.which) {
case 9:
k(u.tab), t.preventDefault();
break;
case 13:
k(`
`), t.preventDefault();
break;
}
}, k = (t) => {
const e = window.getSelection(), l = e.getRangeAt(0), c = document.createTextNode(t);
l.insertNode(c), e.collapseToEnd();
}, E = (t) => {
if (!x.value.includes(u.language)) return t;
const [e, l] = z();
return f(t.replace(/&/g, "&")).replace(new RegExp(e, "gsi"), (c, ...n) => {
n = n.slice(0, n.length - 2);
let a;
const i = u.language === "pug";
let r = n.find((p, s) => p && (a = l[s]) && p);
if (["punctuation", "quote", "htmlentity"].includes(a)) r = f(r);
else if (a === "comment")
if (i) {
const [p, s, d] = n.slice(l.indexOf("comment"));
r = `${p}${s}${f(d)}`;
} else r = f(r);
else if (a === "text" && i) {
let [p, s, d] = n;
return d = v(d), `${p}<span class="punctuation">|</span>${s}<span class="text">${d}</span>`;
} else if (a === "text2" && i) {
const [, , , p, s, d] = n, P = E(s);
return `${p}${P}<span class="punctuation">.</span>
<span class="text">${d}</span>`;
} else {
if (a === "tag" && ["xml", "html", "html-vue", "pug"].includes(u.language))
return N(n.slice(l.indexOf("tag")));
if (a === "variable" && r[0] === "." && u.language === "js")
return `<span class="punctuation">.</span><span class="obj-attr">${r.substr(1)}</span>`;
}
let g = "";
if (a === "color" && u.language === "css") {
const { dark: p, alpha: s } = O(r);
a += ` color--${p ? "dark" : "light"}${s < 0.4 || r === "transparent" ? " color--transparent" : ""}`, g = ` style="background-color: ${r}"`;
}
return a && `<span class="${a}"${g}>${r}</span>` || "";
});
}, B = () => T.default && y(T.default()) || "", H = j(() => E(B())), F = (t) => {
t.target.insertAdjacentHTML(
"afterend",
`<textarea id="clipboard-textarea">${b.value.innerText}</textarea>`
);
const e = document.getElementById("clipboard-textarea");
e.select(), e.setSelectionRange(0, 99999), document.execCommand("copy"), e.remove(), L("copied", b.value.innerText);
};
return (t, e) => ($(), C("div", {
class: G(["ssh-pre", { "ssh-pre--dark": m.dark }]),
"data-type": m.language,
"data-label": m.label || null
}, [
m.copyButton ? ($(), C("button", {
key: 0,
class: "ssh-pre__copy",
onClick: F
}, [
W(t.$slots, "copy-button", {}, () => [
e[1] || (e[1] = R("Copy", -1))
])
])) : V("", !0),
e[2] || (e[2] = R()),
K("pre", {
ref_key: "codeEl",
ref: b,
class: "ssh-pre__content",
contenteditable: m.editable ? "true" : "false",
innerHTML: H.value,
onKeydown: e[0] || (e[0] = (l) => m.editable && U(l)),
onInput: M
}, null, 40, X)
], 10, Y));
}
};
export {
J as default
};