UNPKG

simple-syntax-highlighter

Version:
326 lines (325 loc) 18.5 kB
import { ref as C, useSlots as j, onMounted as G, onBeforeUpdate as W, openBlock as R, createElementBlock as A, normalizeClass as V, renderSlot as q, createTextVNode as w, createCommentVNode as K, createElementVNode as L } from "vue"; const Y = ["data-type", "data-label"], X = ["contenteditable"], Q = { class: "ssh-pre__original" }, Z = { __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(b, { emit: O }) { const o = { quote: /("(?:\\"|[^"])*")|('(?:\\'|[^'])*')/, // Match simple and double quotes by pair. comment: /(\/\/.*?(?:\n|$)|\/\*.*?(?:\*\/|$))/, // Trailing (// ...) or blocks (/* ... */) comments. doctype: /(&lt;!DOCTYPE.*?&gt;)/, // 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 &amp; inside // quoted attribute value (`attr="& < >"` or `attr='& < >'`). htmlTag: /&lt;(?:([a-z][\w:-]*)((?:[\w\- ]+=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))&gt;/, htmlentity: /(&amp;(?:[a-z]+|#x?\d+);)/, // Punctuation outside of html tags, quotes and comments. punctuation: new RegExp("(!==?|(?:[[\\](){}.:,+\\-?=!])+|(?<!&(?:[a-z]+|#x?\\d+));|\\|\\||&lt;|&gt;|&amp;)"), 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: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: o.htmlTag }, html: { doctype: o.doctype, quote: o.quote, comment: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: o.htmlTag }, "html-vue": { doctype: o.doctype, quote: o.quote, comment: /(&lt;!--.*?(?:--&gt;|$))/, htmlentity: o.htmlentity, tag: /&lt;(?:([a-z][\w:-]*)((?:[\w-]+\s*=(?:"[^"]*"|'[^']*'))*|(?:[^&]|&(?:lt|amp|quot|apos|gt);)*?)(\s*\/?)|(\/?)([a-z][\w:-]*))&gt;/ }, // @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]|&gt;)+)(?=\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: /([:,;{}@#()!]+|&lt;|&gt;|&amp;)/ }, 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])|\|\||&lt;|&gt;|&amp;)/, // 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: /(&lt;\?(?:php)?|\?&gt;|__(?: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/ } }, x = { 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 }, z = { 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 = b, M = O, T = C(Object.keys(h)), d = C(null), v = j(), E = (t) => t.map((e) => { if (!e.children || typeof e.children == "string") return e.children || ""; if (Array.isArray(e.children)) return E(e.children); if (e.children.default) return E(e.children.default()); }).join(""), f = (t) => t.replace(/[<>]/g, (e) => ({ "<": "&lt;", ">": "&gt;" })[e]), D = (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], p = s.length === 3 || s.length === 4; l = parseInt(p ? s[0] + s[0] : s.substring(0, 2), 16) / 255, c = parseInt(p ? s[1] + s[1] : s.substring(2, 4), 16) / 255, n = parseInt(p ? s[2] + s[2] : s.substring(4, 6), 16) / 255, (s.length === 4 || s.length === 8) && (a = parseInt(p ? s[3] + s[3] : s.substring(6, 8), 16) / 255); } const m = 0.2126 * e(l) + 0.7152 * e(c) + 0.0722 * e(n); return { dark: a * m + (1 - a) * 1 <= 0.45, alpha: a }; }, U = () => { let t = ""; const e = []; for (const l in h[u.language]) { const c = z[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, (m) => `<span class="id">${m}</span>`).replace(/\.[\w-]+/g, (m) => `<span class="class">${m}</span>`), n && (n = n.replace(x.pug, e), n = '<span class="punctuation">(</span>' + n + '<span class="punctuation">)</span>'), r && (r = I(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(x[u.language], e); return ( // The tag opening: `</` or `<`. `<span class="punctuation">&lt;${a}</span><span class="tag-name">${l || i}</span>` + r + // The tag end `>` or `/>`. `<span class="punctuation">${n}&gt;</span>` ); } }, I = (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>'), B = () => { if (T.value.includes(u.language)) { const t = F(); d.value.innerHTML = y(d.value.innerText), k(d.value.childNodes, t); } }, F = () => { const t = window.getSelection(); t.collapseToEnd(); const e = new Range(); return e.setStart(d.value, 0), e.setEnd(t.extentNode, t.extentOffset), e.toString().length; }, k = (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 ? k(a.childNodes, e - l) : document.getSelection().setPosition(((n = a.childNodes) == null ? void 0 : n[0]) || a, e - l); break; } l += i; } }, H = (t) => { switch (t.which) { case 9: $(u.tab), t.preventDefault(); break; case 13: $(` `), t.preventDefault(); break; } }, $ = (t) => { const e = window.getSelection(), l = e.getRangeAt(0), c = document.createTextNode(t); l.insertNode(c), e.collapseToEnd(); }, y = (t) => { if (!T.value.includes(u.language)) return t; const [e, l] = U(); return f(t.replace(/&/g, "&amp;")).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((g, s) => g && (a = l[s]) && g); if (["punctuation", "quote", "htmlentity"].includes(a)) r = f(r); else if (a === "comment") if (i) { const [g, s, p] = n.slice(l.indexOf("comment")); r = `${g}${s}${f(p)}`; } else r = f(r); else if (a === "text" && i) { let [g, s, p] = n; return p = I(p), `${g}<span class="punctuation">|</span>${s}<span class="text">${p}</span>`; } else if (a === "text2" && i) { const [, , , g, s, p] = n, _ = y(s); return `${g}${_}<span class="punctuation">.</span> <span class="text">${p}</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 m = ""; if (a === "color" && u.language === "css") { const { dark: g, alpha: s } = D(r); a += ` color--${g ? "dark" : "light"}${s < 0.4 || r === "transparent" ? " color--transparent" : ""}`, m = ` style="background-color: ${r}"`; } return a && `<span class="${a}"${m}>${r}</span>` || ""; }); }, S = () => v.default && E(v.default()) || "", P = (t) => { t.target.insertAdjacentHTML( "afterend", `<textarea id="clipboard-textarea">${d.value.innerText}</textarea>` ); const e = document.getElementById("clipboard-textarea"); e.select(), e.setSelectionRange(0, 99999), document.execCommand("copy"), e.remove(), M("copied", d.value.innerText); }; return G(() => { const t = S(); d.value.innerText = t, d.value.innerHTML = y(d.value.innerText); }), W(() => { d.value && (d.value.innerHTML = y(S())); }), (t, e) => (R(), A("div", { class: V(["ssh-pre", { "ssh-pre--dark": b.dark }]), "data-type": b.language, "data-label": b.label || null }, [ b.copyButton ? (R(), A("button", { key: 0, class: "ssh-pre__copy", onClick: P }, [ q(t.$slots, "copy-button", {}, () => [ e[1] || (e[1] = w("Copy")) ]) ])) : K("", !0), e[2] || (e[2] = w()), L("pre", { ref_key: "codeEl", ref: d, class: "ssh-pre__content", contenteditable: b.editable ? "true" : "false", onKeydown: e[0] || (e[0] = (l) => b.editable && H(l)), onInput: B }, " ", 40, X), e[3] || (e[3] = w()), L("div", Q, [ q(t.$slots, "default") ]) ], 10, Y)); } }; export { Z as default };