{"version":3,"sources":["jsdelivr-header.js","/npm/@unified-latex/libs/replace-node.ts","/npm/@unified-latex/libs/utils/significant-node.ts","/npm/@unified-latex/libs/utils/join-without-excess-whitespace.ts","/npm/@unified-latex/libs/utils/wrap-significant-content.ts","/npm/@unified-latex/libs/utils/replace-streaming-command-in-array.ts","/npm/@unified-latex/libs/replace-streaming-command.ts","/npm/@unified-latex/libs/replace-node-during-visit.ts","/npm/@unified-latex/libs/unified-latex-streaming-command.ts"],"names":[],"mappings":"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","file":"/npm/@unified-latex/unified-latex-util-replace@1.8.3/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/@unified-latex/unified-latex-util-replace@1.8.3/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { visit, VisitInfo } from \"@unified-latex/unified-latex-util-visit\";\n\n/**\n * Recursively replace nodes in `ast`. The `visitor` function is called on each node. If\n * `visitor` returns a node or an array of nodes, those nodes replace the node passed to `visitor`.\n * If `null` is returned, the node is deleted. If `undefined` is returned, no replacement happens.\n */\nexport function replaceNode(\n    ast: Ast.Ast,\n    visitor: (\n        node: Ast.Node | Ast.Argument,\n        info: VisitInfo\n    ) =>\n        | Ast.Node\n        | Ast.Argument\n        | (Ast.Node | Ast.Argument)[]\n        | null\n        | undefined\n        | void\n) {\n    visit(ast, {\n        leave: (node, info) => {\n            let replacement = visitor(node, info);\n            // Returning `undefined` or the same node means we shouldn't replace that node\n            if (typeof replacement === \"undefined\" || replacement === node) {\n                return;\n            }\n\n            if (!info.containingArray || info.index == null) {\n                throw new Error(\n                    \"Trying to replace node, but cannot find containing array\"\n                );\n            }\n\n            if (\n                replacement === null ||\n                (Array.isArray(replacement) && replacement.length === 0)\n            ) {\n                // A null return means that we delete the current node\n                info.containingArray.splice(info.index, 1);\n                return info.index;\n            }\n\n            if (!Array.isArray(replacement)) {\n                replacement = [replacement];\n            }\n\n            info.containingArray.splice(info.index, 1, ...replacement);\n            // We don't want to *reprocess* the nodes we just inserted into the array,\n            // lest we get stuck in a recursive loop if the replacement contains the original.\n            // Thus we jump to the index after our replacements.\n            return info.index + replacement.length;\n        },\n    });\n}\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\n\n/**\n * Returns the first non-whitespace/non-comment node in `nodes`. If there is no such\n * node, `null` is returned.\n */\nexport function firstSignificantNode(\n    nodes: Ast.Node[],\n    parbreaksAreInsignificant?: boolean\n): Ast.Node | null {\n    const index = firstSignificantNodeIndex(nodes, parbreaksAreInsignificant);\n    if (index == null) {\n        return null;\n    }\n    return nodes[index];\n}\n\n/**\n * Returns the last non-whitespace/non-comment node in `nodes`. If there is no such\n * node, `null` is returned.\n */\nexport function lastSignificantNode(\n    nodes: Ast.Node[],\n    parbreaksAreInsignificant?: boolean\n): Ast.Node | null {\n    const index = lastSignificantNodeIndex(nodes, parbreaksAreInsignificant);\n    if (index == null) {\n        return null;\n    }\n    return nodes[index];\n}\n\n/**\n * Returns the index of the last non-whitespace/non-comment node in `nodes`. If there is no such\n * node, `null` is returned.\n */\nexport function lastSignificantNodeIndex(\n    nodes: Ast.Node[],\n    parbreaksAreInsignificant?: boolean\n): number | undefined {\n    for (let i = nodes.length - 1; i >= 0; i--) {\n        const node = nodes[i];\n        if (\n            match.whitespace(node) ||\n            match.comment(node) ||\n            (parbreaksAreInsignificant && match.parbreak(node))\n        ) {\n            continue;\n        }\n        return i;\n    }\n    return undefined;\n}\n\n/**\n * Returns the index of the first non-whitespace/non-comment node in `nodes`. If there is no such\n * node, `null` is returned.\n */\nexport function firstSignificantNodeIndex(\n    nodes: Ast.Node[],\n    parbreaksAreInsignificant?: boolean\n): number | undefined {\n    for (let i = 0; i < nodes.length; i++) {\n        const node = nodes[i];\n        if (\n            match.whitespace(node) ||\n            match.comment(node) ||\n            (parbreaksAreInsignificant && match.parbreak(node))\n        ) {\n            continue;\n        }\n        return i;\n    }\n    return undefined;\n}\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\nimport { trimStart } from \"@unified-latex/unified-latex-util-trim\";\n\n/**\n * Is the node space-like? I.e., is it whitespace or\n * a comment with leading whitespace?\n */\nfunction isSpaceLike(node: Ast.Node): boolean {\n    return (\n        match.whitespace(node) ||\n        (match.comment(node) && Boolean(node.leadingWhitespace))\n    );\n}\n\n/**\n * Similar to `head.push(...tail)` except that whitespace at the start\n * of `tail` and the end of `head` is collapsed.\n */\nexport function joinWithoutExcessWhitespace(\n    head: Ast.Node[],\n    tail: Ast.Node[]\n): void {\n    if (tail.length === 0) {\n        return;\n    }\n    if (head.length === 0) {\n        head.push(...tail);\n        return;\n    }\n    const headEnd = head[head.length - 1];\n    const tailStart = tail[0];\n    // Whitespace we can just trim off from either end\n    if (match.whitespace(headEnd) && match.whitespace(tailStart)) {\n        head.push(...tail.slice(1));\n        return;\n    }\n    // If there's no whitespace at one of the ends, no need to worry\n    // unless `tailStart` is a comment, in which case it should \"eat\"\n    // the whitespace\n    if (!isSpaceLike(headEnd) || !isSpaceLike(tailStart)) {\n        if (match.whitespace(headEnd) && match.comment(tailStart)) {\n            const comment: Ast.Comment = {\n                type: \"comment\",\n                content: tailStart.content,\n                sameline: true,\n                leadingWhitespace: true,\n            };\n            tail = tail.slice(1);\n            trimStart(tail);\n            head.pop();\n            head.push(comment, ...tail);\n            return;\n        }\n        head.push(...tail);\n        return;\n    }\n\n    // If we're here, we have a comment with leading whitespace on one side\n    // and whitespace/comments on the other.\n    if (match.comment(headEnd) && match.comment(tailStart)) {\n        if (tailStart.leadingWhitespace || tailStart.sameline) {\n            head.push(\n                { type: \"comment\", content: tailStart.content },\n                ...tail.slice(1)\n            );\n            return;\n        }\n        head.push(...tail);\n        return;\n    }\n\n    // Exactly one side is a comment, so we should trim the whitespace and keep the comment,\n    // but make sure the comment has leading whitespace!\n    let comment = match.comment(headEnd) ? headEnd : tailStart;\n    if (!match.comment(comment)) {\n        throw new Error(\n            `Expected a comment but found ${JSON.stringify(comment)}`\n        );\n    }\n\n    if (!comment.leadingWhitespace || !comment.sameline) {\n        comment = {\n            type: \"comment\",\n            content: comment.content,\n            leadingWhitespace: true,\n            sameline: true,\n        };\n    }\n\n    head.pop();\n    head.push(comment, ...tail.slice(1));\n}\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\n\n/**\n * Wraps `content` in the specified wrapper. This command is roughly equivalent to\n * `wrapper(content)` except that leading and trailing whitespace and comments are extracted\n * from `content` and moved to the front or back of the return array. For example,\n * `[\" \", \"foo\", \"bar\", \"% xxx\"]` -> `[\" \", wrapped([\"foo\", \"bar\"]), \"% xxx\"]`.\n *\n */\nexport function wrapSignificantContent(\n    content: Ast.Node[],\n    wrapper: (content: Ast.Node[]) => Ast.Node[] | Ast.Node\n): Ast.Node[] {\n    let hoistUntil = 0;\n    let hoistAfter = content.length;\n    for (let i = 0; i < content.length; i++) {\n        if (match.whitespace(content[i]) || match.comment(content[i])) {\n            hoistUntil = i + 1;\n            continue;\n        }\n        break;\n    }\n    for (let j = content.length - 1; j >= 0; j--) {\n        if (match.whitespace(content[j]) || match.comment(content[j])) {\n            hoistAfter = j;\n            continue;\n        }\n        break;\n    }\n\n    if (hoistUntil === 0 && hoistAfter === content.length) {\n        return ensureArray(wrapper(content));\n    }\n\n    const frontMatter = content.slice(0, hoistUntil);\n    const middle = content.slice(hoistUntil, hoistAfter);\n    const backMatter = content.slice(hoistAfter, content.length);\n\n    return frontMatter.concat(wrapper(middle), backMatter);\n}\n\nfunction ensureArray(x: Ast.Node | Ast.Node[]) {\n    if (!Array.isArray(x)) {\n        return [x];\n    }\n    return x;\n}\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { trimEnd, trimStart } from \"@unified-latex/unified-latex-util-trim\";\nimport { joinWithoutExcessWhitespace } from \"./join-without-excess-whitespace\";\nimport { wrapSignificantContent } from \"./wrap-significant-content\";\n\n/**\n * Replace commands identified by `isStreamingCommand` with the return value of `replacer`.\n * E.g., the array `[head, streamingCommand, ...tail]` will become `[head, replacer(tail, streamingCommand)]`.\n * This function does not split based on parbreaks/etc.. It is right-associative and returns\n * the streaming commands that were encountered.\n */\nexport function replaceStreamingCommandInArray(\n    nodes: Ast.Node[],\n    isStreamingCommand: (node: any) => node is Ast.Macro,\n    replacer: (\n        content: Ast.Node[],\n        streamingCommand: Ast.Macro\n    ) => Ast.Node | Ast.Node[]\n): { foundStreamingCommands: Ast.Node[] } {\n    // Streaming commands that come at the end don't do anything,\n    // so we should remove them\n    while (nodes.length > 0 && isStreamingCommand(nodes[nodes.length - 1])) {\n        nodes.pop();\n        trimEnd(nodes);\n    }\n\n    const foundStreamingCommands: Ast.Node[] = [];\n\n    for (let i = nodes.length - 1; i >= 0; i--) {\n        const node = nodes[i];\n        if (isStreamingCommand(node)) {\n            const wrapper = (content: Ast.Node[]) => replacer(content, node);\n            let tail = nodes.slice(i + 1);\n            // Streaming commands are followed by whitespace, which becomes unneeded when the commands are replaced.\n            trimStart(tail);\n            tail = wrapSignificantContent(tail, wrapper);\n            foundStreamingCommands.push(node);\n\n            // Trim off what we're about to replace!\n            nodes.splice(i);\n\n            joinWithoutExcessWhitespace(nodes, tail);\n        }\n    }\n\n    return { foundStreamingCommands };\n}\n","import * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\nimport {\n    splitOnCondition,\n    unsplitOnMacro,\n} from \"@unified-latex/unified-latex-util-split\";\nimport {\n    trim,\n    trimEnd,\n    trimStart,\n} from \"@unified-latex/unified-latex-util-trim\";\nimport { firstSignificantNode } from \"./utils/significant-node\";\nimport { replaceStreamingCommandInArray } from \"./utils/replace-streaming-command-in-array\";\nimport { wrapSignificantContent } from \"./utils/wrap-significant-content\";\n\ntype Replacer = (nodes: Ast.Node[]) => Ast.Node[];\n\n/**\n * Process streaming commands in a group. If needed, \"escape\" the group.\n * For example, `{\\bfseries xx}` -> `\\textbf{xx}`, but `{foo \\bfseries xx}` -> `{foo \\textbf{xx}}`.\n */\nexport function replaceStreamingCommandInGroup(\n    group: Ast.Group,\n    isStreamingCommand: (node: any) => node is Ast.Macro,\n    replacer: (\n        content: Ast.Node[],\n        streamingCommand: Ast.Macro\n    ) => Ast.Node | Ast.Node[],\n    options?: {\n        macrosThatBreakPars?: string[];\n        environmentsThatDontBreakPars?: string[];\n    }\n): Ast.Node[] {\n    const content = group.content;\n    // If the group started with a streaming command, we want to pop\n    // out of the group. E.g. `{\\bfseries foo}` -> `\\textbf{foo}` and not `{\\textbf{foo}}`\n    let popFromGroup = isStreamingCommand(firstSignificantNode(content));\n\n    let innerProcessed = replaceStreamingCommand(\n        content,\n        isStreamingCommand,\n        replacer,\n        options\n    );\n\n    // If the group consisted of just streaming commands (for some reason...)\n    // it should be eliminated\n    if (innerProcessed.length === 0) {\n        return [];\n    }\n\n    if (popFromGroup) {\n        return innerProcessed;\n    } else {\n        return [{ type: \"group\", content: innerProcessed }];\n    }\n}\n\n/**\n * Given a group or a node array, look for streaming commands (e.g., `\\bfseries`) and replace them\n * with the specified macro. The \"arguments\" of the streaming command are passed to `replacer` and the return\n * value of `replacer` is inserted into the stream.\n *\n * By default, this command will split at parbreaks (since commands like `\\textbf{...} do not accept parbreaks in their\n * contents) and call `replacer` multiple times, once per paragraph.\n *\n * Commands are also split at environments and at any macros listed in `macrosThatBreakPars`.\n */\nexport function replaceStreamingCommand(\n    ast: Ast.Group | Ast.Node[],\n    isStreamingCommand: (node: any) => node is Ast.Macro,\n    replacer: (\n        content: Ast.Node[],\n        streamingCommand: Ast.Macro\n    ) => Ast.Node | Ast.Node[],\n    options?: {\n        macrosThatBreakPars?: string[];\n        environmentsThatDontBreakPars?: string[];\n    }\n): Ast.Node[] {\n    if (typeof isStreamingCommand !== \"function\") {\n        throw new Error(\n            `'isStreamingCommand' must be a function, not '${typeof isStreamingCommand}'`\n        );\n    }\n\n    const {\n        macrosThatBreakPars = [\n            \"part\",\n            \"chapter\",\n            \"section\",\n            \"subsection\",\n            \"subsubsection\",\n            \"vspace\",\n            \"smallskip\",\n            \"medskip\",\n            \"bigskip\",\n            \"hfill\",\n        ],\n        environmentsThatDontBreakPars = [],\n    } = options || {};\n\n    let processedContent: Ast.Node[] = [];\n    if (match.group(ast)) {\n        processedContent = replaceStreamingCommandInGroup(\n            ast,\n            isStreamingCommand,\n            replacer\n        );\n    }\n\n    if (Array.isArray(ast)) {\n        // Streaming commands that come at the end of a sequence of nodes don't do anything.\n        // They also will consume whitespace, so we should remove them and the whitespace.\n        const nodes = ast;\n        let scanIndex = nodes.length;\n        let sliceIndex = scanIndex;\n        while (\n            scanIndex > 0 &&\n            (isStreamingCommand(nodes[scanIndex - 1]) ||\n                match.whitespace(nodes[scanIndex - 1]))\n        ) {\n            scanIndex--;\n            if (isStreamingCommand(nodes[scanIndex])) {\n                sliceIndex = scanIndex;\n            }\n        }\n        if (sliceIndex !== nodes.length) {\n            nodes.splice(sliceIndex);\n        }\n\n        const macroThatBreaks = match.createMacroMatcher(macrosThatBreakPars);\n        const envThatDoesntBreak = match.createEnvironmentMatcher(\n            environmentsThatDontBreakPars\n        );\n        // A \"par\" is anything that a streaming command shouldn't wrap and breaks the stream.\n        // This includes regular pars, but also environments and special macros like \\section\n        const isPar = (node: Ast.Node) =>\n            match.parbreak(node) ||\n            match.macro(node, \"par\") ||\n            macroThatBreaks(node) ||\n            (match.environment(node) && !envThatDoesntBreak(node)) ||\n            node.type === \"displaymath\";\n\n        // We split on both a parbreak and a literal `\\par`. But we will\n        // normalize everything to be parbreaks\n        const splitByPar = splitOnCondition(nodes, isPar);\n        splitByPar.separators = splitByPar.separators.map((sep) =>\n            match.macro(sep, \"par\") ? { type: \"parbreak\" } : sep\n        );\n\n        const replacers: Replacer[] = [];\n        let segments = splitByPar.segments.map((segment) => {\n            if (segment.length === 0) {\n                return segment;\n            }\n            function applyAccumulatedReplacers(nodes: Ast.Node[]): Ast.Node[] {\n                if (replacers.length === 0) {\n                    return nodes;\n                }\n                return wrapSignificantContent(\n                    nodes,\n                    composeReplacers(replacers)\n                );\n            }\n\n            const { foundStreamingCommands } = replaceStreamingCommandInArray(\n                segment,\n                isStreamingCommand,\n                replacer\n            );\n\n            // All streaming commands in `segment` have now been replaced. However,\n            // there might be commands from the previous paragraphs that should wrap\n            // the current segment!\n            const ret = applyAccumulatedReplacers(segment);\n\n            // Any streaming commands from this segment will carry over to the next,\n            // so keep track of them.\n            foundStreamingCommands.forEach((macro) => {\n                replacers.push((nodes: Ast.Node[]) => {\n                    const ret = replacer(nodes, macro as Ast.Macro);\n                    if (!Array.isArray(ret)) {\n                        return [ret];\n                    }\n                    return ret;\n                });\n            });\n\n            return ret;\n        });\n\n        // Leading/trailing whitespace was hoisted in front/back of each replacer.\n        // Since we're separated by parbreaks, we can safely trim all that whitespace.\n        if (segments.length > 1) {\n            segments.forEach((segment, i) => {\n                if (i === 0) {\n                    trimEnd(segment);\n                } else if (i === segments.length - 1) {\n                    trimStart(segment);\n                } else {\n                    trim(segment);\n                }\n            });\n        }\n\n        processedContent = unsplitOnMacro({\n            segments: segments,\n            macros: splitByPar.separators,\n        });\n    }\n\n    return processedContent;\n}\n\n/**\n * Given a sequence of replacer functions `[f, g, h]` return\n * `h \\circ g \\circ f`\n *\n * @param {((nodes: Ast.Node[]) => Ast.Node)[]} replacers\n * @returns {(nodes: Ast.Node[]) => Ast.Node}\n */\nfunction composeReplacers(replacers: Replacer[]): Replacer {\n    if (replacers.length === 0) {\n        throw new Error(\"Cannot compose zero replacement functions\");\n    }\n    return (nodes: Ast.Node[]) => {\n        let ret = nodes;\n        for (let i = 0; i < replacers.length; i++) {\n            const func = replacers[i];\n            ret = func(ret);\n        }\n        return ret;\n    };\n}\n","import { VisitInfo } from \"@unified-latex/unified-latex-util-visit\";\nimport * as Ast from \"@unified-latex/unified-latex-types\";\n\n/**\n * Replaces the current node with `replacement`. It is assumed that the current\n * node is in an array that is a child of a parent element. If this is not the case,\n * the function will error.\n */\nexport function replaceNodeDuringVisit(\n    replacement: Ast.Node | Ast.Argument | (Ast.Node | Ast.Argument)[],\n    info: VisitInfo\n) {\n    const parent = info.parents[0];\n    if (!parent) {\n        throw new Error(`Cannot replace node: parent not found`);\n    }\n    const container = parent[info.key as keyof typeof parent] as\n        | (Ast.Node | Ast.Argument)[]\n        | undefined;\n    if (!Array.isArray(container)) {\n        throw new Error(`Cannot replace node: containing array not found`);\n    }\n    if (info.index == null) {\n        throw new Error(`Cannot replace node: node index undefined`);\n    }\n    if (!Array.isArray(replacement)) {\n        container[info.index] = replacement;\n    } else {\n        container.splice(info.index, 1, ...replacement);\n    }\n}\n","import { Plugin } from \"unified\";\nimport * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\nimport { trimEnd, trimStart } from \"@unified-latex/unified-latex-util-trim\";\nimport { visit } from \"@unified-latex/unified-latex-util-visit\";\nimport { replaceStreamingCommand } from \"./replace-streaming-command\";\nimport { replaceNodeDuringVisit } from \"./replace-node-during-visit\";\n\ntype PluginOptions = {\n    replacers: Record<\n        string,\n        (\n            content: Ast.Node[],\n            streamingCommand: Ast.Macro\n        ) => Ast.Node | Ast.Node[]\n    >;\n};\n\n/**\n * Unified plugin to replace all found streaming commands with their argument-style equivalents.\n * This only applies to sections of the tree with no math ancestor.\n *\n * @param options.replacer A record of macro names and replacer functions. A replacer function accepts content and the original streaming command and is expected to return the argument-style command. It may be called multiple times per streaming command.\n */\nexport const unifiedLatexReplaceStreamingCommands: Plugin<\n    PluginOptions[],\n    Ast.Root,\n    Ast.Root\n> = function unifiedLatexReplaceStreamingCommands(options) {\n    const { replacers = {} } = options || {};\n    const isReplaceable = match.createMacroMatcher(replacers);\n    return (tree) => {\n        // We traverse all groups before we replace nodes in other contexts\n        visit(\n            tree,\n            (group, info) => {\n                if (\n                    info.context.hasMathModeAncestor ||\n                    !group.content.some(isReplaceable)\n                ) {\n                    return;\n                }\n\n                let fixed = replaceStreamingCommand(\n                    group,\n                    isReplaceable,\n                    (content, command) => {\n                        return replacers[command.content](content, command);\n                    }\n                );\n\n                // We cannot replace the node unless we can access the containing array.\n                if (!info.containingArray || info.index == null) {\n                    return;\n                }\n\n                // `fixed` may consist of only whitespace. If this is the case,\n                // surrounding whitespace must trimmed before\n                // inserting the group's contents.\n                const prevToken = info.containingArray[info.index - 1];\n                const nextToken = info.containingArray[info.index + 1];\n                if (\n                    match.whitespaceLike(prevToken) &&\n                    match.whitespaceLike(fixed[0])\n                ) {\n                    trimStart(fixed);\n                }\n                if (\n                    match.whitespaceLike(nextToken) &&\n                    match.whitespaceLike(fixed[fixed.length - 1])\n                ) {\n                    trimEnd(fixed);\n                }\n                replaceNodeDuringVisit(fixed, info);\n            },\n            { test: match.group }\n        );\n\n        visit(\n            tree,\n            (nodes, info) => {\n                if (\n                    info.context.hasMathModeAncestor ||\n                    !nodes.some(isReplaceable)\n                ) {\n                    return;\n                }\n\n                const replaced = replaceStreamingCommand(\n                    nodes,\n                    isReplaceable,\n                    (content, command) => {\n                        return replacers[command.content](content, command);\n                    }\n                );\n\n                // If we get back a different array than we input, the replacement did\n                // not happen in-place. In this case we need to manipulate `nodes`.\n                if (replaced !== nodes) {\n                    nodes.length = 0;\n                    nodes.push(...replaced);\n                }\n            },\n            { includeArrays: true, test: Array.isArray }\n        );\n    };\n};\n"]}