mathpix-markdown-it
Version:
Mathpix-markdown-it is an open source implementation of the mathpix-markdown spec written in Typescript. It relies on the following open source libraries: MathJax v3 (to render math with SVGs), markdown-it (for standard Markdown parsing)
356 lines • 14.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listSetCounterInline = exports.listItemInline = exports.listBeginInline = exports.listCloseInline = exports.latexListEnvInline = exports.findFirstCompleteListEnv = void 0;
var tslib_1 = require("tslib");
var utils_1 = require("../utils");
var list_state_1 = require("./list-state");
var latex_list_types_1 = require("./latex-list-types");
var latex_list_common_1 = require("./latex-list-common");
var latex_list_env_engine_1 = require("./latex-list-env-engine");
var consts_1 = require("../common/consts");
/**
* Finds the first complete list environment starting at `startPos`.
* - Tracks nested itemize/enumerate via `listStack`
* - Treats lstlisting/tabular as opaque (skips their content)
* - Skips Markdown backtick code spans so `\begin/\end` inside code does not interfere
*/
var findFirstCompleteListEnv = function (src, startPos) {
var _a, _b, _c;
var slice = src.slice(startPos);
var begin = slice.match(consts_1.BEGIN_LIST_ENV_RE);
if (!begin || begin.index !== 0) {
return null;
}
var rootTypeRaw = ((_a = begin[1]) !== null && _a !== void 0 ? _a : "").trim();
if (!rootTypeRaw || !(0, latex_list_types_1.isListType)(rootTypeRaw)) {
return null;
}
var rootType = rootTypeRaw;
var listStack = [rootType];
var pos = startPos + begin[0].length;
// Opaque env stack: tabular can nest; lstlisting cannot.
var opaqueStack = [];
while (pos < src.length) {
// 1) Skip Markdown code spans starting exactly at pos
var codePos = (0, utils_1.skipBackticks)(src, pos);
if (codePos !== pos) {
pos = codePos;
continue;
}
var rest = src.slice(pos);
// 2) If inside opaque → only look for END of the current opaque
if (opaqueStack.length > 0) {
var top_1 = opaqueStack[opaqueStack.length - 1];
var endRe = top_1 === "lstlisting" ? consts_1.END_LST_INLINE_RE : consts_1.END_TABULAR_INLINE_RE;
endRe.lastIndex = 0;
var me = endRe.exec(rest);
if (!me) {
// continue scanning char-by-char until we find the end
pos += 1;
continue;
}
// Found opaque end, pop stack and jump after it
pos += me.index + me[0].length;
opaqueStack = opaqueStack.slice(0, -1);
continue;
}
// 3) Nested begin list (must be exactly at pos)
var mbList = rest.match(consts_1.BEGIN_LIST_ENV_RE);
if (mbList && mbList.index === 0) {
var tRaw = ((_b = mbList[1]) !== null && _b !== void 0 ? _b : "").trim();
if (tRaw && (0, latex_list_types_1.isListType)(tRaw)) {
listStack.push(tRaw);
pos += mbList[0].length;
continue;
}
}
// 4) End list (must be exactly at pos)
var meList = rest.match(consts_1.END_LIST_ENV_RE);
if (meList && meList.index === 0) {
var tRaw = ((_c = meList[1]) !== null && _c !== void 0 ? _c : "").trim();
if (!tRaw || !(0, latex_list_types_1.isListType)(tRaw)) {
return null;
}
var expected = listStack[listStack.length - 1];
if (expected !== tRaw) {
return null;
}
listStack.pop();
pos += meList[0].length;
if (listStack.length === 0) {
return { type: rootType, start: startPos, end: pos, raw: src.slice(startPos, pos) };
}
continue;
}
// 5) Opaque begin (ONLY if starts exactly at pos)
consts_1.BEGIN_LST_INLINE_RE.lastIndex = 0;
consts_1.BEGIN_TABULAR_INLINE_RE.lastIndex = 0;
var mbLst = consts_1.BEGIN_LST_INLINE_RE.exec(rest);
var mbTab = consts_1.BEGIN_TABULAR_INLINE_RE.exec(rest);
var mbLst0 = mbLst && mbLst.index === 0 ? mbLst : null;
var mbTab0 = mbTab && mbTab.index === 0 ? mbTab : null;
if (mbLst0 || mbTab0) {
var opened = mbLst0 ? "lstlisting" : "tabular";
opaqueStack = tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(opaqueStack), false), [opened], false);
pos += (mbLst0 !== null && mbLst0 !== void 0 ? mbLst0 : mbTab0)[0].length;
continue;
}
pos += 1;
}
return null;
};
exports.findFirstCompleteListEnv = findFirstCompleteListEnv;
/**
* Inline rule: recognizes a complete `\begin{itemize|enumerate}...\end{...}` sequence at the current
* cursor, parses it with the block list parser, then injects the resulting tokens into the inline stream.
* Any token.inlinePos produced by the block parser is shifted to absolute offsets in `state.src`.
*/
var latexListEnvInline = function (state, silent) {
var _a;
var startPos = state.pos;
// Must start with '\'
if (state.src.charCodeAt(startPos) !== 0x5c /* '\' */) {
return false;
}
var begin = state.src.slice(startPos).match(consts_1.BEGIN_LIST_ENV_RE);
if (!begin || begin.index !== 0) {
return false;
}
var type = ((_a = begin[1]) !== null && _a !== void 0 ? _a : "").trim();
if (!type || !(0, latex_list_types_1.isListType)(type)) {
return false;
}
var env = (0, exports.findFirstCompleteListEnv)(state.src, startPos);
if (!env) {
return false;
}
if (silent) {
state.pos = env.end;
return true;
}
// Parse raw env using block logic
var parsed = (0, latex_list_env_engine_1.parseListEnvRawToTokens)(state.md, env.raw, state.env);
if (!parsed.ok) {
return false;
}
// Flush tokens into inline stream and shift inlinePos by absolute start offset
(0, latex_list_env_engine_1.flushTokensToInline)(state, parsed.tokens, env.start);
// Advance position
state.pos = env.end;
return true;
};
exports.latexListEnvInline = latexListEnvInline;
/**
* Inline rule that parses LaTeX list environment closing commands:
*
* \end{itemize}
* \end{enumerate}
*
* It:
* - checks that we are in block/list context,
* - closes any still-open list item (`latex_list_item_close`),
* - emits `itemize_list_close` or `enumerate_list_close`,
* - updates `state.level` and `state.prentLevel`,
* - updates internal list-level state via `leaveListLevel`,
* - advances `state.pos` to the end of the `\end{...}` command.
*/
var listCloseInline = function (state, silent) {
var _a;
var startPos = state.pos;
// Only handle in block/list context
if (!state.env.isBlock) {
return false;
}
// Must start with backslash
if (state.src.charCodeAt(startPos) !== 0x5c /* '\' */) {
return false;
}
var match = state.src
.slice(startPos)
.match(consts_1.END_LIST_ENV_RE);
if (!match) {
return false;
}
var rawType = match[1].trim();
if (!(0, latex_list_types_1.isListType)(rawType)) {
return false;
}
if (!silent) {
var listType = rawType;
(0, latex_list_common_1.closeOpenListItemIfNeeded)(state);
var _b = (0, latex_list_common_1.getListTokenTypes)(listType), closeType = _b.closeType, htmlTag = _b.htmlTag;
// itemize_list_close or enumerate_list_close
var token = state.push(closeType, htmlTag, -1);
(0, latex_list_common_1.applyListCloseState)(state, token);
}
state.pos = startPos + ((_a = match.index) !== null && _a !== void 0 ? _a : 0) + match[0].length;
return true;
};
exports.listCloseInline = listCloseInline;
/**
* Inline rule that parses LaTeX list environment openings:
*
* \begin{itemize}
* \begin{enumerate}
*
* It:
* - validates that we are in block/list context,
* - emits an `itemize_list_open` or `enumerate_list_open` token,
* - updates `state.prentLevel`, `state.parentType` and `state.types`,
* - advances `state.pos` to the end of the \begin{...} command,
* - registers the new list level in the list-level state.
*/
var listBeginInline = function (state, silent) {
var _a;
var startPos = state.pos;
// Only inside block/list context
if (!state.env.isBlock) {
return false;
}
// Must start with backslash
if (state.src.charCodeAt(startPos) !== 0x5c /* '\' */) {
return false;
}
var match = state.src
.slice(startPos)
.match(consts_1.BEGIN_LIST_ENV_RE);
if (!match) {
return false;
}
var rawType = match[1].trim();
if (!(0, latex_list_types_1.isListType)(rawType)) {
return false;
}
if (!silent) {
var listType = rawType;
var _b = (0, latex_list_common_1.getListTokenTypes)(listType), openType = _b.openType, htmlTag = _b.htmlTag;
// itemize_list_open or enumerate_list_open
var token = state.push(openType, htmlTag, 1);
(0, latex_list_common_1.applyListOpenState)(state, listType, token);
}
state.pos = startPos + ((_a = match.index) !== null && _a !== void 0 ? _a : 0) + match[0].length;
return true;
};
exports.listBeginInline = listBeginInline;
/**
* Inline rule that parses a single LaTeX list item:
* \item[marker] content...
*
* It:
* - closes a previously open list item if necessary,
* - opens a new `latex_list_item_open` token,
* - parses the optional marker into `markerTokens`,
* - creates an `inline` token with the item content,
* - updates `state.pos` to the end of the current item.
*/
var listItemInline = function (state, silent) {
var startPos = state.pos;
// Must start with backslash
if (state.src.charCodeAt(startPos) !== 0x5c /* '\' */) {
return false;
}
// Only handle in block/list context
if (!state.env.isBlock) {
return false;
}
// Try to match \item[...] command right after '\'
var itemMatch = state.src
.slice(startPos)
.match(consts_1.LATEX_ITEM_COMMAND_RE);
if (!itemMatch) {
return false;
}
// Find where this item ends: next \item or begin/end list env
var boundaryMatch = state.src
.slice(startPos + itemMatch.index + itemMatch[0].length)
.match(consts_1.LATEX_LIST_BOUNDARY_INLINE_RE);
var content = boundaryMatch && boundaryMatch.index > 0
? state.src.slice(startPos + itemMatch.index + itemMatch[0].length, startPos + itemMatch.index + itemMatch[0].length + boundaryMatch.index)
: state.src.slice(startPos + itemMatch.index + itemMatch[0].length);
if (!silent) {
// Close previous <li> if needed
(0, latex_list_common_1.closeOpenListItemIfNeeded)(state);
// Open new list item
var token = state.push("latex_list_item_open", "li", 1);
(0, list_state_1.incrementItemCount)();
token.parentType = state.parentType;
token.inlinePos = {
start_content: startPos + itemMatch.index + itemMatch[0].length,
};
// Skip leading spaces in content for accurate inline range
token.inlinePos.start_content += (0, utils_1.getSpacesFromLeft)(content);
token.inlinePos.end_content = token.inlinePos.start_content + content.length;
// Optional marker: \item[<marker>]
if (itemMatch[1] !== undefined) {
token.marker = itemMatch[1] ? itemMatch[1].trim() : "";
var children = [];
var beforeOptions = tslib_1.__assign({}, state.md.options);
if (state.md.options.forDocx) {
state.md.options = tslib_1.__assign(tslib_1.__assign({}, state.md.options), { outMath: {
include_svg: true,
include_mathml_word: false,
} });
}
state.md.inline.parse(itemMatch[1], state.md, state.env, children);
state.md.options = beforeOptions;
token.markerTokens = children;
}
// Inline content inside the list item
token = state.push("inline", "", 0);
token.content = content.trim();
token.children = [];
}
// Advance parser position to after this item
state.pos = startPos + itemMatch.index + itemMatch[0].length + content.length;
return true;
};
exports.listItemInline = listItemInline;
/**
* Inline rule that parses LaTeX \setcounter commands inside list environments:
*
* \setcounter{enumi}{3}
*
* It:
* - validates that we are in block/list context (state.env.isBlock),
* - parses the numeric value,
* - converts N to N+1 (so the next list item starts from that value),
* - emits a `setcounter` token with `content = "<nextNumber>"`,
* - optionally attaches the original LaTeX source in `token.latex`
* when `md.options.forLatex` is enabled.
*
* Example:
* \setcounter{enumi}{3} → token.type = "setcounter", token.content = "4"
*/
var listSetCounterInline = function (state, silent) {
var _a, _b, _c, _d;
// Only handle in block/list context (not in pure inline text)
if (!state.env.isBlock) {
return false;
}
var startPos = state.pos;
// Must start with backslash
if (state.src.charCodeAt(startPos) !== 0x5c /* '\' */) {
return false;
}
var match = state.src
.slice(startPos)
.match(consts_1.reSetCounter);
if (!match) {
return false;
}
if (!silent) {
var startNumber = (0, latex_list_common_1.parseSetCounterNumber)(match);
var content = startNumber.toString();
var token = state.push("setcounter", "", 0);
token.content = content;
if ((_b = (_a = state.md) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.forLatex) {
var absoluteEnd = startPos + ((_c = match.index) !== null && _c !== void 0 ? _c : 0) + match[0].length;
token.latex = state.src.slice(state.pos, absoluteEnd);
}
}
// Advance parser position to just after the \setcounter call
state.pos = startPos + ((_d = match.index) !== null && _d !== void 0 ? _d : 0) + match[0].length;
return true;
};
exports.listSetCounterInline = listSetCounterInline;
//# sourceMappingURL=latex-list-env-inline.js.map