CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/src/packages/frontend/markdown/math-plugin.ts
Views: 561
/*1This is a revamp of https://github.com/goessner/markdown-it-texmath for our purposes.2The original license with MIT, and we consider our modified version of it to also3be MIT licensed.45TODO: move this to a separate package hosted on npmjs.67Original copyright:8* Copyright (c) Stefan Goessner - 2017-21. All rights reserved.9* Licensed under the MIT License. See License.txt in the project root for license information.1011CHANGES we made:1213- We don't care about using katex for rendering, so that code is gone.14We only care about parsing.151617- RULES:1819The markdown-it-texmath plugin is very impressive, but it doesn't parse20things like \begin{equation}x^3$\end{equation} without dollar signs.21However, that is a basic requirement for cocalc in order to preserve22Jupyter classic compatibility. So we define our own rules, inspired23by the dollars rules from the the plugin,24and extend the regexps to also recognize these. We do this with a new25object "cocalc", to avoid potential conflicts.26IMPORTANT: We remove the math_block_eqno from upstream, since it27leads to very disturbing behavior and loss of information, e.g.,28$$x$$2930(a) xyz31Gets rendered with the xyz gone. Very confusing. Equation numbers32when we do them, should be done as in latex, not with some weird notation that33is surprising. See https://github.com/sagemathinc/cocalc/issues/587934*/3536const texmath = {37inline: (rule) =>38function inline(state, silent) {39const pos = state.pos;40const str = state.src;41const pre =42str.startsWith(rule.tag, (rule.rex.lastIndex = pos)) &&43(!rule.pre || rule.pre(str, pos)); // valid pre-condition ...44const match = pre && rule.rex.exec(str);45const res =46!!match &&47pos < rule.rex.lastIndex &&48(!rule.post || rule.post(str, rule.rex.lastIndex - 1));4950if (res) {51if (!silent) {52const token = state.push(rule.name, "math", 0);53token.content = match[1];54token.markup = rule.tag;55}56state.pos = rule.rex.lastIndex;57}58return res;59},6061block: (rule) =>62function block(state, begLine, endLine, silent) {63const pos = state.bMarks[begLine] + state.tShift[begLine];64const str = state.src;65const pre =66str.startsWith(rule.tag, (rule.rex.lastIndex = pos)) &&67(!rule.pre || rule.pre(str, false, pos)); // valid pre-condition ....68const match = pre && rule.rex.exec(str);69const res =70!!match &&71pos < rule.rex.lastIndex &&72(!rule.post || rule.post(str, false, rule.rex.lastIndex - 1));7374if (res && !silent) {75// match and valid post-condition ...76const endpos = rule.rex.lastIndex - 1;77let curline;7879for (curline = begLine; curline < endLine; curline++)80if (81endpos >= state.bMarks[curline] + state.tShift[curline] &&82endpos <= state.eMarks[curline]83) {84// line for end of block math found ...85break;86}87// "this will prevent lazy continuations from ever going past our end marker"88// https://github.com/markdown-it/markdown-it-container/blob/master/index.js89const lineMax = state.lineMax;90const parentType = state.parentType;91state.lineMax = curline;92state.parentType = "math";9394if (parentType === "blockquote") {95// remove all leading '>' inside multiline formula96match[1] = match[1].replace(/(\n*?^(?:\s*>)+)/gm, "");97}98// begin token99let token = state.push(rule.name, "math", 0); // 'math_block'100token.block = true;101token.tag = rule.tag;102token.markup = "";103token.content = match[1];104token.map = [begLine, curline + 1]; // WARNING: this +1 is also fixes an upstream bug. Getting this right is critical for caching in slate.105// end token ... superfluous ...106107state.parentType = parentType;108state.lineMax = lineMax;109state.line = curline + 1;110}111return res;112},113render: (tex, displayMode) => {114// We need to continue to support rendering to MathJax as an option,115// but texmath only supports katex. Thus we output by default to116// html using script tags, which are then parsed later using our117// katex/mathjax plugin.118return `<script type="math/tex${119displayMode ? "; mode=display" : ""120}">${tex}</script>`;121},122123// used for enable/disable math rendering by `markdown-it`124inlineRuleNames: ["math_inline", "math_inline_double"],125blockRuleNames: ["math_block"],126127rules: {128cocalc: {129inline: [130{131name: "math_inline_double",132rex: /\${2}([^$]*?[^\\])\${2}/gy,133tag: "$$",134displayMode: true,135pre,136post,137},138{139// We modify this from what's included in markdown-it-texmath to allow for140// multiple line inline formulas, e.g., "$2+\n3$" should work, but doesn't in upstream.141name: "math_inline",142rex: /\$((?:[^\$\s\\])|(?:[\S\s]*?[^\\]))\$/gmy,143tag: "$",144outerSpace: false,145pre,146post,147},148{149// using \begin/\end as part of inline markdown...150name: "math_inline",151rex: /(\\(?:begin)(\{math\})[\s\S]*?\\(?:end)\2)/gmy,152tag: "\\",153displayMode: false,154pre,155post,156},157{158// using \begin/\end as part of inline markdown...159name: "math_inline_double",160rex: /(\\(?:begin)(\{[a-z]*\*?\})[\s\S]*?\\(?:end)\2)/gmy,161tag: "\\",162displayMode: true,163pre,164post,165},166],167block: [168{169name: "math_block",170rex: /\${2}([^$]*?[^\\])\${2}/gmy,171tag: "$$",172},173{174name: "math_block",175rex: /(\\(?:begin)(\{[a-z]*\*?\})[\s\S]*?\\(?:end)\2)/gmy, // regexp to match \begin{...}...\end{...} environment.176tag: "\\",177},178],179},180},181};182183export default function mathPlugin(md) {184for (const rule of texmath.rules["cocalc"].inline) {185md.inline.ruler.before("escape", rule.name, texmath.inline(rule)); // ! important186md.renderer.rules[rule.name] = (tokens, idx) =>187texmath.render(tokens[idx].content, !!rule.displayMode);188}189190for (const rule of texmath.rules["cocalc"].block) {191md.block.ruler.before("fence", rule.name, texmath.block(rule)); // ! important for ```math delimiters192md.renderer.rules[rule.name] = (tokens, idx) =>193texmath.render(tokens[idx].content, true);194}195}196197function pre(str, beg) {198const prv = beg > 0 ? str[beg - 1].charCodeAt(0) : false;199return (200!prv ||201(prv !== 0x5c && // no backslash,202(prv < 0x30 || prv > 0x39))203); // no decimal digit .. before opening '$'204}205206function post(str, end) {207const nxt = str[end + 1] && str[end + 1].charCodeAt(0);208return !nxt || nxt < 0x30 || nxt > 0x39; // no decimal digit .. after closing '$'209}210211212