"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/diff.ts function diff(textLines, dataLines, getText, addition) { let i = 0; for (; i < textLines.length; i++) { const text = _nullishCoalesce(_optionalChain([getText, 'optionalCall', _ => _(i)]), () => ( dataLines[i])); if (textLines[i] !== text || _optionalChain([addition, 'optionalCall', _2 => _2(i)])) { break; } } let j = textLines.length - 1; let k = dataLines.length - 1; for (; j >= 0 && k >= 0; j--, k--) { const text = _nullishCoalesce(_optionalChain([getText, 'optionalCall', _3 => _3(k)]), () => ( dataLines[k])); if (textLines[j] !== text || _optionalChain([addition, 'optionalCall', _4 => _4(k)])) { break; } } if (i > j) { [i, j] = [j, i]; } i = Math.max(0, i); j = Math.min(textLines.length - 1, j) + 1; if (textLines.length === dataLines.length && i + 1 === j) { return [i, j]; } return expand([...textLines], i, j); } function expand(chars, start, end) { const side = matchTwoSides(Math.max(0, start + end - chars.length)); start -= side; end += side; function matchTwoSides(offset) { const pos = chars.slice(0, start).indexOf(chars[end], offset); if (pos === -1) { return 0; } for (let j = pos, k = end; j < start - 1; k++, j++) { if (chars[j + 1] !== chars[k + 1]) { return matchTwoSides(offset + 1); } } return start - pos; } const texts = chars.join("\n"); const startOffset = chars.slice(0, start).join("\n").length; const endOffset = chars.slice(0, end).join("\n").length; const left = []; for (let i = start - 1, it = endOffset - 1; i < end && it >= startOffset; i--) { const text = chars[i]; const idx = texts.lastIndexOf(text, it); if (idx !== -1 && idx >= startOffset) { left.push(text); it = idx - 1; } else break; } const right = []; for (let i = end, it = startOffset; i >= start && it < endOffset; i++) { const text = chars[i]; const idx = texts.indexOf(text, it); if (idx !== -1 && idx < endOffset) { right.push(text); it = idx + 1; } else break; } return [start - left.length, end + right.length]; } // src/utils.ts function throttle(func, delay) { let start = 0; let timer = void 0; return function(...args) { if (timer) { clearTimeout(timer); timer = void 0; } const now = performance.now(); if (!start || now - start >= delay) { func.apply(this, args); start = now; } else { timer = setTimeout(() => { func.apply(this, args); start = performance.now(); timer = void 0; }, delay + start - now); } }; } function once(func) { let called = false; return function(...args) { if (called) { return false; } called = true; func.apply(this, args); return true; }; } function isArrayEqual(a, b) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false; } } return true; } // src/index.ts function createPlainShiki(shiki) { const isSupported = () => "CSS" in globalThis && "highlights" in CSS; function mount(el, options) { const { lang, themes = { light: "min-light", dark: "min-dark" }, defaultTheme = "light", selector = (theme) => `.${theme}`, defaultSelector = ":root", watch = true, delay = 33.4 } = options; const stylesheet = new CSSStyleSheet(); document.adoptedStyleSheets.push(stylesheet); const colorRanges = /* @__PURE__ */ new Map(); const loadLines = []; function patch(loads, oldLoads) { for (const { range, name } of walkTokens(oldLoads)) { const highlight = CSS.highlights.get(name); _optionalChain([highlight, 'optionalAccess', _5 => _5.delete, 'call', _6 => _6(range)]); const ranges = colorRanges.get(name); _optionalChain([ranges, 'optionalAccess', _7 => _7.delete, 'call', _8 => _8(range)]); } for (const { range, color, theme, name } of walkTokens(loads)) { const isDefault = theme === defaultTheme; let highlight = CSS.highlights.get(name); if (!highlight) { CSS.highlights.set(name, highlight = new Highlight()); highlight.priority = isDefault ? 0 : 1; } let ranges = colorRanges.get(name); if (!ranges) { colorRanges.set(name, ranges = /* @__PURE__ */ new Set()); const rule = `${isDefault ? defaultSelector : selector(theme)}::highlight(${name}) { color: ${color}; }`; stylesheet.insertRule(rule); } highlight.add(range); ranges.add(range); } } function tempUpdate(textLines, start, end) { if (end - start > 1 || loadLines.length <= start) { return; } const textLine = textLines[start]; const loadLine = loadLines[start]; const result = diff(textLine, loadLine.text); const left = loadLine.offset + result[0]; const right = loadLine.offset + result[1]; const load = _optionalChain([loadLine, 'access', _9 => _9.loads, 'optionalAccess', _10 => _10.find, 'call', _11 => _11(({ range: range2 }) => left >= range2.startOffset && left < range2.endOffset)]); if (!load) { return; } const { range } = load; range.setEnd(range.endContainer, Math.max(range.endOffset, right)); } const fullUpdate = throttle((innerText, textLines, textNodes, start, end) => { const length = end - textLines.length + loadLines.length; const chunk = loadLines.splice(length); for (let i = start; i < length; i++) { patch([], _nullishCoalesce(_optionalChain([loadLines, 'access', _12 => _12[i], 'optionalAccess', _13 => _13.loads]), () => ( []))); } const lastGrammarState = _optionalChain([loadLines, 'access', _14 => _14.at, 'call', _15 => _15(-1), 'optionalAccess', _16 => _16.lastGrammarState]); loadLines.length = end - 1; loadLines.fill(null, start, end - 1); loadLines.push(createLoadLine({ lastGrammarState }), ...chunk); let offset = textLines.slice(0, start).reduce((res, text) => res + text.length + 1, 0); const findNodeAndOffset = createFindNodeAndOffset(innerText, textNodes, offset); for (let i = start; i < textLines.length; i++) { const text = textLines[i]; const tokenResult = shiki.codeToTokens(text, { lang, themes, cssVariablePrefix: "", defaultColor: false, grammarState: _optionalChain([loadLines, 'access', _17 => _17[i - 1], 'optionalAccess', _18 => _18.lastGrammarState]) }); const loads = []; for (const token of tokenResult.tokens[0]) { const [startNode, startOffset] = findNodeAndOffset(offset + token.offset); const [endNode, endOffset] = findNodeAndOffset(offset + token.offset + token.content.length); const range = document.createRange(); range.setStart(startNode, startOffset); range.setEnd(endNode, endOffset); loads.push({ token, range }); } const loadLine = loadLines[i] ??= createLoadLine(); patch(loads, loadLine.loads); loadLine.loads = loads; loadLine.text = text; loadLine.offset = offset; const oldScopes = _nullishCoalesce(_optionalChain([loadLine, 'access', _19 => _19.lastGrammarState, 'optionalAccess', _20 => _20.getScopes, 'call', _21 => _21()]), () => ( [Number.NaN])); const newScopes = _nullishCoalesce(_optionalChain([tokenResult, 'access', _22 => _22.grammarState, 'optionalAccess', _23 => _23.getScopes, 'call', _24 => _24()]), () => ( [Number.NaN])); const skip = isArrayEqual(oldScopes, newScopes); loadLine.lastGrammarState = tokenResult.grammarState; if (!skip) { offset += text.length + 1; } else break; } }, delay); if (isSupported()) { watch && el.addEventListener("input", update); update(); } const dispose = once(() => { watch && el.removeEventListener("input", update); const idx = document.adoptedStyleSheets.indexOf(stylesheet); document.adoptedStyleSheets.splice(idx, 1); for (const [name, ranges] of colorRanges) { const highlight = CSS.highlights.get(name); for (const range of ranges) { _optionalChain([highlight, 'optionalAccess', _25 => _25.delete, 'call', _26 => _26(range)]); } } }); function update() { const { innerText } = el; const textLines = innerText.split("\n"); const textNodes = collectTextNodes(el); const [start, end] = diff( textLines, loadLines, (i) => _optionalChain([loadLines, 'access', _27 => _27[i], 'optionalAccess', _28 => _28.text]), (i) => _optionalChain([loadLines, 'access', _29 => _29[i], 'optionalAccess', _30 => _30.loads, 'access', _31 => _31.some, 'call', _32 => _32(({ range }) => range.collapsed)]) ); tempUpdate(textLines, start, end); fullUpdate(innerText, textLines, textNodes, start, end); } return { dispose, update }; } return { get isSupported() { return isSupported(); }, mount }; } function* walkTokens(loads) { for (const { token, range } of loads) { if (typeof token.htmlStyle !== "object") { continue; } for (const theme in token.htmlStyle) { const color = token.htmlStyle[theme]; const name = `shiki-${theme}-${color.slice(1).toLowerCase()}`; yield { range, color, theme, name }; } } } function collectTextNodes(el) { const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); const textNodes = []; let node; while (node = walker.nextNode()) { textNodes.push(node); } return textNodes; } function createLoadLine(options = {}) { return { text: "", offset: 0, lastGrammarState: void 0, loads: [], ...options }; } function createFindNodeAndOffset(innerText, textNodes, initialOffset) { let i = 0; let offset = 0; let isCorrect = false; if (initialOffset > 0) { find(initialOffset); } return find; function find(tokenOffset) { for (; i < textNodes.length; i++) { const node = textNodes[i]; const { textContent } = node; if (!textContent) { continue; } if (!isCorrect) { offset = innerText.indexOf(textContent, offset); isCorrect = true; } if (offset + textContent.length < tokenOffset) { offset += textContent.length; isCorrect = false; } else break; } return [textNodes[i], tokenOffset - offset]; } } exports.createPlainShiki = createPlainShiki;