diff --git a/examples/languages/test.css b/examples/languages/test.css index a50982c..32e282f 100644 --- a/examples/languages/test.css +++ b/examples/languages/test.css @@ -1,20 +1,405 @@ -@import "my-styles.css"; +/* TODO Test Modern CSS Features */ +@charset "UTF-8"; -p > a, -.class, -#id { - color: blue; - text-decoration: underline; +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"); +@import "animations.css" layer(animations); + +@layer reset, base, components, utilities; + +@namespace url(http://www.w3.org/1999/xhtml); +@namespace svg url(http://www.w3.org/2000/svg); + +@font-face { + font-family: "CustomFont"; + src: url("font.woff2") format("woff2"), + url("font.woff") format("woff"); + font-weight: 100 900; + font-display: swap; +} + +@property --gradient-angle { + syntax: ""; + initial-value: 0deg; + inherits: false; +} + +@property --my-color { + syntax: ""; + initial-value: #c0ffee; + inherits: true; +} + +@counter-style custom-counter { + /* test */ + system: cyclic; + symbols: ◉ ◌ ◎; + suffix: " "; +} + +:root { + --primary: oklch(0.7 0.2 200); + --secondary: color-mix(in oklch, var(--primary) 80%, white); + --spacing-xs: 0.25rem; + --spacing-sm: clamp(0.5rem, 2vw, 1rem); + --grid-gap: max(1rem, 2%); + --border-radius: round(up, 12px, 4px); +} + +@supports (container-type: inline-size) { + .card { + container-type: inline-size; + container-name: card; + } +} + +/* Nesting & Scoping */ +container h2 { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); + gap: var(--grid-gap); + + & > h2 { + background: light-dark(white, #1a1a1a); + padding: var(--spacing-sm); + border: 1px solid color-mix(in srgb, currentColor 20%, transparent); + + &:hover h2 { + scale: 1.05; + rotate: 2deg; + translate: 0 -5px; + } + + &:has(> img) { + aspect-ratio: 16 / 9; + } + + @media (width >= 768px) { + padding: calc(var(--spacing-sm) * 2); + } + } +} + +/* Container Queries */ +@container card (min-width: 400px) { + .card-title { + font-size: clamp(1.5rem, 4cqw, 2.5rem); + } +} + +/* Advanced Selectors */ +:is(article, section, aside):has(> h2) { + margin-block: 2rem; +} + +:where(.button, .link):not(:disabled, [aria-disabled="true"]) { + cursor: pointer; + + &:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; + } +} + +/* Cascade Layers */ +@layer components { + .button { + all: unset; + padding: 0.5em 1em; + background: linear-gradient(135deg, var(--primary), var(--secondary)); + color: white; + border-radius: var(--border-radius); + + &[data-variant="ghost"] { + background: none; + color: var(--primary); + border: 1px solid currentColor; + } + } +} + +/* Complex Animations */ +@keyframes spin-colors { + from { + --gradient-angle: 0deg; + filter: hue-rotate(0deg); + } + to { + --gradient-angle: 360deg; + filter: hue-rotate(360deg); + } +} + +@keyframes slide-fade { + 0% { + opacity: 0; + translate: -100% 0; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 1; + translate: 0 0; + } +} + +/* Scroll-driven Animations */ +@supports (animation-timeline: scroll()) { + .parallax { + animation: parallax linear; + animation-timeline: scroll(); + } + + @keyframes parallax { + to { + translate: 0 -50%; + } + } +} + +/* View Transitions */ +@view-transition { + navigation: auto; +} + +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 0.3s; +} + +/* Complex Functions & Calculations */ +.complex-layout { + width: calc(100% - var(--spacing-sm) * 2); + height: max(50vh, 400px); + padding: clamp(1rem, 5vw, 3rem); + margin-inline: auto; + + background: + linear-gradient( + calc(var(--gradient-angle) + 45deg), + oklch(0.7 0.2 calc(var(--gradient-angle) * 1)), + oklch(0.5 0.3 calc(var(--gradient-angle) * 2)) + ); + + clip-path: polygon( + 0 0, + 100% 0, + 100% calc(100% - 2rem), + calc(100% - 2rem) 100%, + 0 100% + ); + + box-shadow: + 0 1px 2px hsl(0 0% 0% / 0.1), + 0 4px 8px hsl(0 0% 0% / 0.1), + 0 12px 24px hsl(0 0% 0% / 0.1); +} + +/* Subgrid */ +.grid-parent { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; + + & .grid-child { + display: grid; + grid-template-columns: subgrid; + grid-column: span 4; + } +} + +/* Logical Properties */ +.card { + padding-block: 2rem; + padding-inline: 1.5rem; + margin-block-end: var(--spacing-sm); + border-inline-start: 4px solid var(--primary); + inset-block-start: 0; + inset-inline-end: 0; } -/*TODO comment*/ -@page :left { - color: #2222; - margin-left: 4cm; - margin-right: 3cm; - background: url(test.jpg); - color: rgb(5, 5, 5); +/* Media Queries */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } } -@media print { - body { font-size: 10pt } + +@media (prefers-color-scheme: dark) { + :root { + --primary: oklch(0.8 0.2 200); + } +} + +@media (orientation: landscape) and (min-width: 1024px) { + .hero { + aspect-ratio: 21 / 9; + } +} + +@media (hover: hover) and (pointer: fine) { + .interactive:hover { + transform: scale(1.1) rotate(5deg); + } +} + +/* Custom Media Queries (future) */ +@custom-media --mobile (width < 768px); +@custom-media --desktop (width >= 1024px); + +/* Feature Queries */ +@supports (backdrop-filter: blur(10px)) { + .glass { + background: rgb(255 255 255 / 0.1); + backdrop-filter: blur(10px) saturate(180%); + -webkit-backdrop-filter: blur(10px) saturate(180%); + } +} + +@supports selector(:has(> img)) { + .card:has(img) { + padding: 0; + } +} + +/* Anchor Positioning */ +.tooltip { + position: absolute; + position-anchor: --trigger; + bottom: anchor(top); + left: anchor(center); + translate: -50% -8px; +} + +/* Advanced Pseudo-classes */ +input:user-invalid { + border-color: red; +} + +dialog:modal { + max-width: 90vw; + max-height: 90vh; +} + +:target-within { + background: color-mix(in srgb, yellow 20%, transparent); +} + +/* Color Functions */ +.color-demo { + background: oklch(0.7 0.2 200 / 0.8); + border-color: lch(50% 50 200); + color: lab(60% 40 30); + box-shadow: 0 4px 12px hwb(200 20% 20% / 0.5); + + &::before { + background: color-mix(in oklch, red 30%, blue); + } + + &::after { + background: color-contrast( + wheat vs tan, sienna, var(--primary), #d2691e + ); + } +} + +/* Relative Colors */ +.relative-colors { + --base: oklch(0.7 0.2 200); + background: oklch(from var(--base) calc(l * 0.8) c h); + border: 1px solid oklch(from var(--base) l calc(c * 1.5) h / 0.5); +} + +/* Math Functions */ +.math { + width: round(nearest, 34.6%, 10%); + height: mod(100vh, 50px); + padding: rem(18px, 16px); + gap: hypot(3em, 4em); + rotate: atan2(1, 1); +} + +/* Trigonometry */ +.trig { + transform: rotate(calc(sin(45deg) * 360deg)); + width: calc(cos(60deg) * 100%); + translate: calc(tan(30deg) * 1rem) 0; +} + +/* Advanced Grid */ +.masonry { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-template-rows: masonry; + align-tracks: start; +} + +/* Scroll Snap */ +.carousel { + scroll-snap-type: x mandatory; + scroll-padding-inline: 1rem; + scrollbar-width: thin; + scrollbar-color: var(--primary) transparent; + overscroll-behavior-x: contain; + + & > * { + scroll-snap-align: start; + scroll-snap-stop: always; + } +} + +/* Text Styling */ +.text { + text-wrap: balance; + text-box-trim: both; + text-box-edge: cap alphabetic; + hanging-punctuation: first last; + initial-letter: 3 2; + + &::first-line { + font-variant: small-caps; + letter-spacing: 0.05em; + } +} + +/* Filters & Blend Modes */ +.effects { + filter: + blur(4px) + brightness(1.2) + contrast(1.1) + drop-shadow(0 4px 12px rgb(0 0 0 / 0.3)) + grayscale(0.2) + hue-rotate(45deg) + invert(0.1) + saturate(1.5); + + mix-blend-mode: multiply; + background-blend-mode: overlay, soft-light; +} + +/* Masks */ +.mask { + mask-image: + linear-gradient(to bottom, black 50%, transparent), + url("mask.svg"); + mask-composite: intersect; + mask-mode: alpha, luminance; + mask-size: 100% 100%, contain; +} + +/* Multiple Backgrounds */ +.multi-bg { + background: + url("pattern.svg") repeat, + linear-gradient(135deg, var(--primary), transparent), + radial-gradient(circle at 30% 50%, color-mix(in srgb, red 20%, transparent), transparent 70%), + conic-gradient(from 90deg at 50% 50%, red, yellow, green, cyan, blue, magenta, red); + background-size: 50px 50px, cover, cover, cover; + background-position: 0 0, center, center, center; + background-blend-mode: overlay, normal, screen, hue; } \ No newline at end of file diff --git a/examples/languages/test.js b/examples/languages/test.js index d7d4017..5a70db1 100644 --- a/examples/languages/test.js +++ b/examples/languages/test.js @@ -1,34 +1,75 @@ -/* Multiline FIX - Comment */ /** -* js doc CHANGED x -* @param {String} param -* @return nothing -*/ -const listener = Deno.listen({ port: 8000 }); -console.log(.8 * 0. * (0.8 * .e8) * 0.e8 * 0x849, 0d0101, 0o987, 8_987_654.1789) -console?.log(`http://localhost:${PORT}/`.match(/:[0-9]{2,4}^/g)); -// TODO other comment -for await (const conn of listener) { - if (false) - break; - (async () => { - const requests = Deno.serveHttp(conn); - for await (const { respondWith } of this.requests.new) { - respondWith(new Response('Hello\ - world')); - } - })(); -} -function test(test) { - let test = () => { console.log(test) }; - return test; + * DataProcessor v2.1.0 + * Tests regex, private fields, async generators, and complex nesting. + */ +import { EventEmitter } from 'events'; + +const CONFIG_SYMBOL = Symbol('orchestrator_config'); + +export default class DataOrchestrator extends EventEmitter { + #internalState = 'idle'; // Private class field + static MAX_RETRIES = 3; + + constructor(options = {}) { + super(); + this[CONFIG_SYMBOL] = { + timeout: 5000, + ...options + }; + } + + /** + * Processes an incoming data stream with a regex filter. + * @param {Array} rawData + */ + async process(rawData) { + this.#internalState = 'processing'; + const regex = /data-id_([0-9a-fA-F]{8})/gi; // Complex Regex + + try { + const results = await Promise.all( + rawData.map(async (item, index) => { + // Template literal with nested expression + const logMsg = `Item #${index + 1}: ${item.toUpperCase()}`; + + if (regex.test(item)) { + return { id: item.match(regex)[0], status: 'valid' }; + } + + return { id: null, status: 'invalid' }; + }) + ); + + this.emit('complete', { + timestamp: Date.now(), + count: results.length + }); + + } catch (error) { + console.error(`Error code: ${error.code ?? 'UNKNOWN_ERR'}`); + } finally { + this.#internalState = 'idle'; + } + } + + // Async Generator for memory-efficient streaming + async *streamResults(items) { + for (const item of items) { + yield new Promise(resolve => + setTimeout(() => resolve(`Processed: ${item}`), 100) + ); + } + } } -export default { - jsonData: a > 5, - match: /test/g, - type: `test - ${'test'}hello - ${test + 2.5}hello`, - 'Hello world': true -} \ No newline at end of file + +// Function demonstrating destructuring and default parameters +const init = ({ host = 'localhost', port = 8080 } = {}) => { + const orchestrator = new DataOrchestrator({ host }); + console.log(`Server running at ${host}:${port}`); + return orchestrator; +}; + +/* Block comment test: + Math: 10 / 2 * (5 + 5) +*/ +const instance = init(); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 93e69e5..b5dccdc 100644 --- a/src/index.js +++ b/src/index.js @@ -129,7 +129,7 @@ export async function highlightText(src, lang, multiline = true, opt = {}) { await tokenize(src, lang, (str, type) => tmp += toSpan(sanitize(str), type)) return multiline - ? `
${'
'.repeat(!opt.hideLineNumbers && src.split('\n').length)}
${tmp}
` + ? `
${'
'.repeat(!opt.hideLineNumbers && (src.match(/\n/g) || []).length + 1)}
${tmp}
` : tmp; } @@ -145,7 +145,7 @@ export async function highlightText(src, lang, multiline = true, opt = {}) { */ export async function highlightElement(elm, lang = elm.className.match(/shj-lang-([\w-]+)/)?.[1], mode, opt) { let txt = elm.textContent; - mode ??= `${elm.tagName == 'CODE' ? 'in' : (txt.split('\n').length < 2 ? 'one' : 'multi')}line`; + mode ??= `${elm.tagName == 'CODE' ? 'in' : (txt.includes('\n') ? 'multi' : 'one')}line`; elm.dataset.lang = lang; elm.className = `${[...elm.classList].filter(className => !className.startsWith('shj-')).join(' ')} shj-lang-${lang} shj-${mode}`; elm.innerHTML = await highlightText(txt, lang, mode == 'multiline', opt); @@ -184,3 +184,17 @@ export let highlightAll = async (opt) => export let loadLanguage = (languageName, language) => { langs[languageName] = language; } + +/** + * Pre-load one or more languages + * + * @async + * @function preLoadLanguage + * @param {...string} languageNames The names of the languages to load + * @returns {Promise} A promise that resolves when all languages have been preloaded + */ +export let preLoadLanguage = async (...languageNames) => { + await Promise.all( + languageNames.map(lang => langs[lang] ? Promise.resolve() : import(`./languages/${lang}.js`).then(module => langs[lang] = module)) + ); +} \ No newline at end of file diff --git a/src/languages/bash.js b/src/languages/bash.js index 173bf90..4c6fa54 100644 --- a/src/languages/bash.js +++ b/src/languages/bash.js @@ -48,4 +48,4 @@ export default [ match: /(?<=\s|^)[\w_]+(?=\s*=)/g }, variable -] +] \ No newline at end of file diff --git a/src/languages/css.js b/src/languages/css.js index 1d6761c..af80fc6 100644 --- a/src/languages/css.js +++ b/src/languages/css.js @@ -1,56 +1,60 @@ export default [ { - match: /\/\*((?!\*\/)[^])*(\*\/)?/g, + match: /\/\*[\s\S]*?\*\/|\/\/.*/g, sub: 'todo' }, { expand: 'str' }, + { + match: /url\((?:(['"])(?:(?!\1).)*\1|[^)]*)\)/g, + sub: [ + { type: 'func', match: /^url/g }, + { type: 'str', match: /[^()]+/g } + ] + }, + { + type: 'type', + match: /@[\w-]+\b/g + }, { type: 'kwd', - match: /@\w+\b|\b(and|not|only|or)\b|\b[a-z-]+(?=[^{}]*{)/g + match: /!important\b/g }, { type: 'var', - match: /\b[\w-]+(?=\s*:)|(::?|\.)[\w-]+(?=[^{}]*{)/g + match: /--[\w-]+/g }, { - type: 'func', - match: /#[\w-]+(?=[^{}]*{)/g + type: 'var', + match: /(?<=^[\s\t]*|[;{(]\s*|\*\/[\s\n\r]*)[\w-]+(?=\s*(?::(?:\s|\n|$|(?=.*[;}]))|<=|>=|<|>|=))/gm }, { type: 'num', - match: /#[\da-f]{3,8}/g + match: /#[\da-fA-F]{3,8}\b/g }, { type: 'num', - match: /\d+(\.\d+)?(cm|mm|in|px|pt|pc|em|ex|ch|rem|vm|vh|vmin|vmax|%)?/g, - sub: [ - { - type: 'var', - match: /[a-z]+|%/g - } - ] + match: /-?(?:\d+\.?\d*|\.\d+)(?:(?:cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|deg|rad|grad|turn|s|ms|fr|dpi|dpcm|dppx|q|lh|rlh|vi|vb|cqw|cqh|cqi|cqb|cqmin|cqmax|dvw|dvh|lvw|lvh|svw|svh)\b|%(?!\w))/gi }, { - match: /url\([^)]*\)/g, - sub: [ - { - type: 'func', - match: /url(?=\()/g - }, - { - type: 'str', - match: /[^()]+/g - } - ] + type: 'num', + match: /-?(?:\d+\.?\d*|\.\d+)(?!\w)/g }, { type: 'func', - match: /\b[a-zA-Z]\w*(?=\s*\()/g + match: /[\w-]+(?=\()/g }, { - type: 'num', - match: /\b[a-z-]+\b/g + type: 'bool', + match: /(?`, 'g'), + sub: [ + { + type: 'var', + match: RegExp(`^<[\/!?]?${name}`, 'g'), + sub: [ + { + type: 'oper', + match: /^<[\/!?]?/g + } + ] + }, + { + match: /style\s*=\s*('[^']*'|"[^"]*")/gi, + sub: [ + { + match: /^style\s*=\s*/gi, + sub: [ + { + type: 'class', + match: /^style/gi + }, + { + type: 'oper', + match: /=/g + } + ] + }, + { + match: /('[^']*'|"[^"]*")/g, + sub: [ + { + type: 'str', // quotes + match: /^['"]|['"]$/g + }, + { + match: /[^"']+/g, + sub: 'css' + } + ] + } + ] + }, + { + match: /on\w+\s*=\s*('[^']*'|"[^"]*")/gi, + sub: [ + { + match: /^on\w+\s*=\s*/gi, + sub: [ + { + type: 'class', + match: /^on\w+/gi + }, + { + type: 'oper', + match: /=/g + } + ] + }, + { + match: /('[^']*'|"[^"]*")/g, + sub: [ + { + type: 'str', // quotes + match: /^['"]|['"]$/g + }, + { + match: /[^"']+/g, + sub: 'js' + } + ] + } + ] + }, + ...xmlElement.sub + ] +}; export default [ { @@ -24,13 +103,13 @@ export default [ sub: [ { match: RegExp(`^`, 'g'), - sub: xmlElement.sub + sub: htmlElement.sub }, { match: RegExp(`${xmlElement.match}|[^]*(?=$)`, 'g'), sub: 'css' }, - xmlElement + htmlElement ] }, { @@ -38,14 +117,15 @@ export default [ sub: [ { match: RegExp(`^`, 'g'), - sub: xmlElement.sub + sub: htmlElement.sub }, { match: RegExp(`${xmlElement.match}|[^]*(?=$)`, 'g'), sub: 'js' }, - xmlElement + htmlElement ] }, + htmlElement, ...xml ] diff --git a/src/languages/js.js b/src/languages/js.js index 3189f12..62a3a19 100644 --- a/src/languages/js.js +++ b/src/languages/js.js @@ -11,27 +11,91 @@ export default [ expand: 'str' }, { - match: /`((?!`)[^]|\\[^])*`?/g, + match: new class { + exec(str) { + let i = this.lastIndex; + for (; i < str.length; i++) { + if (str[i] === '`') { + let start = i, stack = 0; + while (++i < str.length) { + if (str[i] === '\\') i++; + else if (str[i] === '$' && str[i + 1] === '{') { stack++; i++; } + else if (stack > 0 && str[i] === '{') stack++; + else if (stack > 0 && str[i] === '}') stack--; + else if (stack === 0 && str[i] === '`') { i++; break; } + } + this.lastIndex = i; + return { index: start, 0: str.slice(start, i) }; + } + } + return null; + } + }(), sub: 'js_template_literals' }, + { + type: 'var', + match: /\b(?!(?:true|false|null|undefined|NaN|Infinity|this|default)\b)[a-zA-Z_$][\w$]*\b(?=\s*:(?!\s*[:=]))/g + }, { type: 'kwd', - match: /=>|\b(this|set|get|as|async|await|break|case|catch|class|const|constructor|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|if|implements|import|in|instanceof|interface|let|var|of|new|package|private|protected|public|return|static|super|switch|throw|throws|try|typeof|void|while|with|yield)\b/g + match: /=>|(?|function\b))/g + }, + { + type: 'kwd', + match: /\bstatic\b(?=\s*\{)/g + }, + { + type: 'var', + match: /\b[a-zA-Z$_][\w$_]*(?=\s*=\s*)/g }, { - expand: 'num' + type: 'var', + match: /(?\+*\-\/%^~]|\b(?:case|else|do|return|throw|typeof|void|yield|await|new|in|instanceof))\s*)\/(?:\\.|\[(?:\\.|\[(?:\\.|[^\]])*\]|[^\]])*\]|(?!\/)[^\r\n\\])+\/[dgimsuyv]*/g, + sub: 'regex' }, { type: 'num', - match: /\b(NaN|null|undefined|[A-Z][A-Z_]*)\b/g + match: /(0[bB][01]+(?:_[01]+)*|0[oO][0-7]+(?:_[0-7]+)*|0[xX][0-9a-fA-F]+(?:_[0-9a-fA-F]+)*|(?:\d+_)*\d+(?:\.(?:\d+_)*\d+)?(?:[eE][+-]?(?:\d+_)*\d+)?)n?/g }, { - type: 'bool', - match: /\b(true|false)\b/g + type: 'var', + match: /#[\w$_]+/g }, { type: 'oper', @@ -40,9 +104,5 @@ export default [ { type: 'class', match: /\b[A-Z][\w_]*\b/g - }, - { - type: 'func', - match: /[a-zA-Z$_][\w$_]*(?=\s*((\?\.)?\s*\(|=\s*(\(?[\w,{}\[\])]+\)? =>|function\b)))/g } ] diff --git a/src/languages/js_template_literals.js b/src/languages/js_template_literals.js index 62675b4..fa49a04 100644 --- a/src/languages/js_template_literals.js +++ b/src/languages/js_template_literals.js @@ -2,21 +2,22 @@ export default [ { match: new class { exec(str) { - let i = this.lastIndex, - j, - f = _ => { - while (++i < str.length - 2) - if (str[i] == '{') f(); - else if (str[i] == '}') return; - }; - for (; i < str.length; ++i) - if (str[i - 1] != '\\' && str[i] == '$' && str[i + 1] == '{') { - j = i++; - f(i); - this.lastIndex = i + 1; - return { index: j, 0: str.slice(j, i + 1) }; + let i = this.lastIndex, j; + for (; i < str.length; ++i) { + if (str[i - 1] !== '\\' && str[i] === '$' && str[i + 1] === '{') { + j = i; i += 2; + let stack = 1; + while (i < str.length && stack > 0) { + if (str[i] === '\\') i++; + else if (str[i] === '{') stack++; + else if (str[i] === '}') stack--; + i++; + } + this.lastIndex = i; + return { index: j, 0: str.slice(j, i) }; + } } - return null; + return null; } }(), sub: [ diff --git a/src/languages/jsdoc.js b/src/languages/jsdoc.js index b519807..9c542ac 100644 --- a/src/languages/jsdoc.js +++ b/src/languages/jsdoc.js @@ -6,8 +6,8 @@ export default [ match: /@\w+/g }, { - type: 'class', - match: /{[\w\s|<>,.@\[\]]+}/g + type: 'type', + match: /{(?:[^}{]|\{(?:[^}{]|\{[^}{]*\})*\})*}/g }, { type: 'var', diff --git a/src/languages/regex.js b/src/languages/regex.js index 1e707dc..f616cbb 100644 --- a/src/languages/regex.js +++ b/src/languages/regex.js @@ -1,19 +1,29 @@ export default [ - { - match: /^(?!\/).*/gm, - sub: 'todo' - }, { type: 'num', - match: /\[((?!\])[^\\]|\\.)*\]/g + match: new class { + exec(str) { + let i = this.lastIndex; + if (str[i] !== '[') return null; + let start = i, stack = 1; + while (++i < str.length && stack > 0) { + if (str[i] === '\\') i++; + else if (str[i] === '[') stack++; + else if (str[i] === ']') stack--; + } + this.lastIndex = i; + return { index: start, 0: str.slice(start, i) }; + } + }(), + sub: 'regex' }, { type: 'kwd', - match: /\||\^|\$|\\.|\w+($|\r|\n)/g + match: /\\.|\||\^|\$|\\[bcdDfnrsStvwWv]|\\p\{[a-zA-Z0-9_=]+\}|\\u\{[a-fA-F0-9]+\}|\\u[a-fA-F0-9]{4}|\\x[a-fA-F0-9]{2}|\w+($|\r|\n)/g }, { type: 'var', - match: /\*|\+|\{\d+,\d+\}/g + match: /\*|\+|\?|\.|\{\d+(?:,\d*)?\}|\\(?=[()])/g } ]; export let type = 'oper'; diff --git a/src/languages/svg.js b/src/languages/svg.js new file mode 100644 index 0000000..9f51873 --- /dev/null +++ b/src/languages/svg.js @@ -0,0 +1,91 @@ +import html, { htmlElement } from './html.js'; +import { name, properties } from './xml.js'; + +const svgElement = { + match: RegExp(`<[\/!?]?${name}${properties}[\/!?]?>`, 'g'), + sub: [ + { + type: 'var', + match: RegExp(`^<[\/!?]?${name}`, 'g'), + sub: [ + { + type: 'oper', + match: /^<[\/!?]?/g + } + ] + }, + { + match: /d\s*=\s*('[^']*'|"[^"]*")/gi, + sub: [ + { + match: /^d\s*=\s*/gi, + sub: [ + { + type: 'class', + match: /^d/gi + }, + { + type: 'oper', + match: /=/g + } + ] + }, + { + match: /('[^']*'|"[^"]*")/g, + sub: [ + { + type: 'str', // quotes + match: /^['"]|['"]$/g + }, + { + type: 'kwd', // commands + match: /[MmZzLlHhVvCcSsQqTtAa]/g + }, + { + type: 'num', // coordinates + match: /-?\d*\.?\d+(?:e[+-]?\d+)?/gi + }, + { + type: 'oper', // separators + match: /[,]/g + } + ] + } + ] + }, + { + match: /viewBox\s*=\s*('[^']*'|"[^"]*")/gi, + sub: [ + { + match: /^viewBox\s*=\s*/gi, + sub: [ + { + type: 'class', + match: /^viewBox/gi + }, + { + type: 'oper', + match: /=/g + } + ] + }, + { + match: /('[^']*'|"[^"]*")/g, + sub: [ + { + type: 'str', // quotes + match: /^['"]|['"]$/g + }, + { + type: 'num', // coordinates + match: /-?\d*\.?\d+/g + } + ] + } + ] + }, + ...htmlElement.sub + ] +}; + +export default html.map(rule => rule === htmlElement ? svgElement : rule); diff --git a/src/languages/xml.js b/src/languages/xml.js index 5cecd1e..c6067e1 100644 --- a/src/languages/xml.js +++ b/src/languages/xml.js @@ -48,7 +48,6 @@ export default [ match: //gi }, xmlElement, - // https://github.com/speed-highlight/core/issues/49 { type: 'str', match: RegExp(`<\\?${name}([^?]|\\?[^?>])*\\?+>`, 'g'),