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)
401 lines • 19.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Lists = exports.ListsInternal = void 0;
var tslib_1 = require("tslib");
var latex_list_tokens_1 = require("./latex-list-tokens");
var latex_list_items_1 = require("./latex-list-items");
var re_level_1 = require("./re-level");
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");
/**
* Detects \begin{lstlisting} or \begin{tabular} on a line and enters an opaque env.
* - Uses `stack` to track nesting (tabular can nest, lstlisting cannot).
* - Text before \begin (including prefixes like \hline or & when nesting inside tabular) is preserved and added as normal list content.
* - From \begin... to end of line is appended as raw/opaque text.
*
* @returns Updated { handled, stack, items, lineText }.
*/
var handleLstBeginInline = function (lineText, stack, items, nextLine, dStart, itemTag) {
var top = stack[stack.length - 1];
// If we are inside lstlisting, ignore any begin markers.
if (top === "lstlisting") {
return { handled: false, stack: stack, items: items, lineText: lineText };
}
// Reset regex lastIndex (important if /g/)
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(lineText);
var mbTab = consts_1.BEGIN_TABULAR_INLINE_RE.exec(lineText);
// If we are inside tabular, allow only nested tabular
if (top === "tabular") {
if (!mbTab)
return { handled: false, stack: stack, items: items, lineText: lineText };
// keep the prefix before \begin{tabular} (e.g. "\hline " or " & ")
var prefix = lineText.slice(0, mbTab.index);
var beginAndRest = lineText.slice(mbTab.index);
// open nested tabular
stack = tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(stack), false), ["tabular"], false);
if (prefix.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, prefix, nextLine);
}
items = (0, latex_list_items_1.ItemsAddToPrev)(items, beginAndRest, nextLine);
return { handled: true, stack: stack, items: items, lineText: lineText };
}
// If stack is empty:
if (!mbLst && !mbTab)
return { handled: false, stack: stack, items: items, lineText: lineText };
// Choose earliest begin if both exist
var mb = mbLst && mbTab
? (mbLst.index <= mbTab.index ? mbLst : mbTab)
: (mbLst || mbTab);
var openedType = mb === mbLst ? "lstlisting" : "tabular";
var beginIndex = mb.index;
var before = lineText.slice(0, beginIndex);
var afterBegin = lineText.slice(beginIndex);
if (before.length > 0) {
if (itemTag.test(before)) {
items = (0, latex_list_items_1.ItemsListPush)(items, before, nextLine + dStart, nextLine + dStart);
}
else {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, before, nextLine);
}
}
stack = tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(stack), false), [openedType], false);
items = (0, latex_list_items_1.ItemsAddToPrev)(items, afterBegin, nextLine);
return { handled: true, stack: stack, items: items, lineText: lineText };
};
/**
* Detects \end{...} for the current opaque env (stack top).
* - If not found, appends the full raw line (keeps indentation) as opaque text.
* - If found, appends up to end marker, pops stack, and returns tail (if any).
*
* @returns Updated { handled, stack, items, lineText }.
*/
var handleLstEndInline = function (lineText, stack, items, nextLine, state) {
var _a;
var top = stack[stack.length - 1];
if (!top) {
return { handled: false, stack: stack, items: items, lineText: lineText };
}
var endRe = top === "lstlisting"
? consts_1.END_LST_INLINE_RE
: consts_1.END_TABULAR_INLINE_RE;
endRe.lastIndex = 0;
var me = endRe.exec(lineText);
if (!me) {
// still inside opaque env → append raw line with indentation
var rawLine = state.src.slice(state.bMarks[nextLine], state.eMarks[nextLine]);
items = (0, latex_list_items_1.ItemsAddToPrev)(items, rawLine, nextLine);
return { handled: true, stack: stack, items: items, lineText: lineText };
}
var endIndex = me.index;
var endToken = lineText.slice(endIndex, endIndex + me[0].length);
var beforeEnd = lineText.slice(0, endIndex);
var afterEnd = lineText.slice(endIndex + me[0].length);
// Append code continuation
if (beforeEnd.length > 0) {
var glue = top === "lstlisting" ? "\n" : "";
items = (0, latex_list_items_1.ItemsAddToPrev)(items, beforeEnd + glue + endToken, nextLine);
}
else {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, endToken, nextLine);
}
// pop matching env
stack = stack.slice(0, -1);
// If nothing meaningful after end tag, consume line
if (!((_a = afterEnd === null || afterEnd === void 0 ? void 0 : afterEnd.trim()) === null || _a === void 0 ? void 0 : _a.length)) {
return { handled: true, stack: stack, items: items, lineText: "" };
}
// return remainder to be parsed normally
return { handled: false, stack: stack, items: items, lineText: afterEnd };
};
/**
* Processes "opaque" inline environments inside list parsing (currently: tabular, lstlisting).
*
* The function may:
* - fully consume the current source line (appending it to `items` as raw text), OR
* - close an opaque env and return a remaining tail to be parsed again on the same line
* (e.g. `\end{tabular} & \begin{tabular}{l}`).
*
* Uses a guard to prevent infinite loops on malformed input.
*/
var processOpaqueLine = function (params) {
var lineText = params.lineText, stack = params.stack, items = params.items, nextLine = params.nextLine, state = params.state, renderStart = params.renderStart;
var guard = 0;
while (guard++ < 50) {
var top_1 = stack[stack.length - 1];
if (top_1) {
// -------- inside opaque --------
if (top_1 === "tabular") {
consts_1.END_TABULAR_INLINE_RE.lastIndex = 0;
consts_1.BEGIN_TABULAR_INLINE_RE.lastIndex = 0;
var me = consts_1.END_TABULAR_INLINE_RE.exec(lineText);
var mb = consts_1.BEGIN_TABULAR_INLINE_RE.exec(lineText);
// close if end exists before begin (or begin missing)
if (me && (!mb || me.index <= mb.index)) {
var endRes_1 = handleLstEndInline(lineText, stack, items, nextLine, state);
stack = endRes_1.stack;
items = endRes_1.items;
if (endRes_1.handled) {
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
}
// got tail → keep parsing same line
lineText = endRes_1.lineText;
continue;
}
// otherwise if begin exists, open nested tabular
if (mb) {
var beginRes_1 = handleLstBeginInline(lineText, stack, items, nextLine, renderStart, consts_1.LATEX_ITEM_COMMAND_INLINE_RE);
stack = beginRes_1.stack;
items = beginRes_1.items;
if (beginRes_1.handled) {
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
}
lineText = beginRes_1.lineText;
continue;
}
// plain opaque line inside tabular:
// preserve indentation unless this is a tail
var rawLine = state.src.slice(state.bMarks[nextLine], state.eMarks[nextLine]);
var rawLineNoIndent = state.src.slice(state.bMarks[nextLine] + state.tShift[nextLine], state.eMarks[nextLine]);
var toAppend = (lineText !== rawLineNoIndent) ? lineText : rawLine;
items = (0, latex_list_items_1.ItemsAddToPrev)(items, toAppend, nextLine);
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
}
// other opaque (lstlisting): only try to end
var endRes = handleLstEndInline(lineText, stack, items, nextLine, state);
stack = endRes.stack;
items = endRes.items;
if (endRes.handled) {
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
}
lineText = endRes.lineText;
continue;
}
// not inside opaque: try to begin
var beginRes = handleLstBeginInline(lineText, stack, items, nextLine, renderStart, consts_1.LATEX_ITEM_COMMAND_INLINE_RE);
stack = beginRes.stack;
items = beginRes.items;
if (beginRes.handled) {
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
}
lineText = beginRes.lineText;
return { consumedLine: false, lineText: lineText, stack: stack, items: items };
}
// safety: if guard exceeded, treat as consumed to avoid infinite loop
items = (0, latex_list_items_1.ItemsAddToPrev)(items, lineText, nextLine);
return { consumedLine: true, lineText: lineText, stack: stack, items: items };
};
/**
* Parse a LaTeX list environment starting at `startLine` and emit tokens into `state`.
*
* Notes:
* - The function is "strict": it returns false if the matching \end{...} is not found.
* - Works with any StateBlock-like object (real block state or synthetic state for inline reuse).
*
* @returns true if the environment was successfully parsed and closed, otherwise false.
*/
var ListsInternal = function (state, startLine, endLine) {
var _a, _b;
var _c, _d, _e;
var pos = state.bMarks[startLine] + state.tShift[startLine];
var max = state.eMarks[startLine];
var lineText = state.src.slice(pos, max);
var renderStart = state.md.options.renderElement && state.md.options.renderElement.startLine
? Number(state.md.options.renderElement.startLine)
: 0;
var oldParentType = state.parentType;
var enumerateLevelTypes = (0, re_level_1.GetEnumerateLevel)();
var dataMarkers = (0, re_level_1.GetItemizeLevelTokensByState)(state);
var itemizeLevelTokens = dataMarkers.tokens;
var itemizeLevelContents = dataMarkers.contents;
var nextLine = startLine;
var li = null;
var openData = (0, latex_list_tokens_1.ListOpen)(state, startLine + renderStart, lineText, itemizeLevelTokens, enumerateLevelTypes, itemizeLevelContents);
var _f = openData.iOpen, iOpen = _f === void 0 ? 0 : _f, _g = openData.tokenStart, tokenStart = _g === void 0 ? null : _g;
li = (_c = openData.li) !== null && _c !== void 0 ? _c : null;
if (iOpen === 0) {
nextLine += 1;
state.line = nextLine;
state.startLine = startLine;
state.parentType = oldParentType;
state.level = state.prentLevel < 0 ? 0 : state.prentLevel;
return true;
}
else {
nextLine += 1;
}
var items = [];
var haveClose = false;
var opaqueStack = [];
for (; nextLine < endLine; nextLine++) {
pos = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
lineText = state.src.slice(pos, max);
// Handle opaque envs; may consume the line or return a tail to re-parse.
var opaqueRes = processOpaqueLine({
lineText: lineText,
stack: opaqueStack,
items: items,
nextLine: nextLine,
state: state,
renderStart: renderStart
});
opaqueStack = opaqueRes.stack;
items = opaqueRes.items;
lineText = opaqueRes.lineText;
if (opaqueRes.consumedLine) {
continue;
}
// Handle \setcounter lines
if (consts_1.reSetCounter.test(lineText)) {
var match = lineText.match(consts_1.reSetCounter);
if (match && ((_d = state.md.options) === null || _d === void 0 ? void 0 : _d.forLatex)) {
var token = state.push("setcounter", "", 0);
token.latex = match[0].trim();
}
if (match && match[2]) {
var sE = match.index + match[0].length < lineText.length
? lineText.slice(match.index + match[0].length)
: "";
sE = sE.trim();
var startNumber = (_e = (0, latex_list_common_1.parseSetCounterNumber)(match)) !== null && _e !== void 0 ? _e : 1;
li = { value: startNumber };
if (sE.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, sE, nextLine);
}
continue;
}
}
// Handle inline \end{itemize}/\end{enumerate}
if (consts_1.END_LIST_ENV_INLINE_RE.test(lineText)) {
var endMatch = lineText.match(consts_1.END_LIST_ENV_INLINE_RE);
if (endMatch) {
var raw = endMatch[1].trim();
if (!(0, latex_list_types_1.isListType)(raw)) {
return false;
}
var _h = (0, latex_list_items_1.splitInlineListEnv)(lineText, endMatch), sB = _h.sB, sE = _h.sE, isBacktickEscapedPair = _h.isBacktickEscapedPair;
if (isBacktickEscapedPair) {
items = (0, latex_list_items_1.ItemsListPush)(items, lineText, nextLine, nextLine);
continue;
}
if (sB.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, sB, nextLine);
}
(_a = (0, latex_list_items_1.finalizeListItems)(state, items, itemizeLevelTokens, enumerateLevelTypes, li, iOpen, itemizeLevelContents, tokenStart), iOpen = _a.iOpen, items = _a.items, li = _a.li);
(0, latex_list_tokens_1.setTokenCloseList)(state, startLine + renderStart, nextLine + renderStart);
if (sE.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, sE, nextLine);
}
iOpen--;
if (iOpen <= 0) {
haveClose = true;
nextLine += 1;
break;
}
}
continue;
}
// Handle inline \begin{itemize}/\begin{enumerate}
if (consts_1.BEGIN_LIST_ENV_INLINE_RE.test(lineText)) {
var beginMatch = lineText.match(consts_1.BEGIN_LIST_ENV_INLINE_RE);
if (beginMatch) {
var raw = beginMatch[1].trim();
if (!(0, latex_list_types_1.isListType)(raw)) {
return false;
}
var beginType = raw;
var _j = (0, latex_list_items_1.splitInlineListEnv)(lineText, beginMatch), sB = _j.sB, sE = _j.sE, isBacktickEscapedPair = _j.isBacktickEscapedPair;
if (isBacktickEscapedPair) {
items = (0, latex_list_items_1.ItemsListPush)(items, lineText, nextLine, nextLine);
continue;
}
if (sB.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, sB, nextLine);
}
(_b = (0, latex_list_items_1.finalizeListItems)(state, items, itemizeLevelTokens, enumerateLevelTypes, li, iOpen, itemizeLevelContents, tokenStart), iOpen = _b.iOpen, items = _b.items, li = _b.li);
(0, latex_list_tokens_1.setTokenOpenList)(state, -1, -1, beginType, itemizeLevelTokens, enumerateLevelTypes, itemizeLevelContents);
if (sE.length > 0) {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, sE, nextLine);
}
iOpen++;
}
}
else {
// Regular line inside list: either a new \item or continuation
if (consts_1.LATEX_ITEM_COMMAND_INLINE_RE.test(lineText)) {
items = (0, latex_list_items_1.ItemsListPush)(items, lineText, nextLine + renderStart, nextLine + renderStart);
}
else {
items = (0, latex_list_items_1.ItemsAddToPrev)(items, lineText, nextLine);
}
}
}
if (!haveClose) {
// Strict mode: do not emit partial tokens (important for inline env wrapper).
// No explicit \end{itemize}/\end{enumerate} found — flush remaining items
return false;
}
state.line = nextLine;
state.startLine = startLine;
state.parentType = oldParentType;
state.level = state.prentLevel < 0 ? 0 : state.prentLevel;
if (tokenStart) {
tokenStart.map[1] = nextLine + renderStart;
}
return true;
};
exports.ListsInternal = ListsInternal;
/**
* Block rule that parses LaTeX list environments:
* \begin{itemize} ... \end{itemize}
* \begin{enumerate} ... \end{enumerate}
*
* It:
* - detects list begin/end commands,
* - collects and splits \item content into logical items,
* - handles \setcounter and nested lists on the same line,
* - emits corresponding *_list_open, *_list_close, and list item tokens.
*/
var Lists = function (state, startLine, endLine, silent) {
var pos = state.bMarks[startLine] + state.tShift[startLine];
var max = state.eMarks[startLine];
var lineText = state.src.slice(pos, max);
// Must start with backslash to be LaTeX command
if (lineText.charCodeAt(0) !== 0x5c /* '\' */) {
return false;
}
var match = lineText.match(consts_1.BEGIN_LIST_ENV_RE);
if (!match) {
return false;
}
var typeList = match[1].trim();
if (!(0, latex_list_types_1.isListType)(typeList)) {
return false;
}
// Buffer tokens first (do not write into the real state during parsing)
var bufferedState = (0, latex_list_env_engine_1.createBufferedState)(state);
// Run the original logic on bufferedState instead of state
var ok = (0, exports.ListsInternal)(bufferedState, startLine, endLine); // we'll define it
if (!ok)
return false;
// In silent mode: only report that this block can start; do not modify state or emit tokens.
if (silent) {
return true;
}
// Flush tokens to the real state at the end
(0, latex_list_env_engine_1.flushBufferedTokens)(state, bufferedState.tokens);
// Sync state fields modified by parsing
state.line = bufferedState.line;
state.startLine = bufferedState.startLine;
state.parentType = bufferedState.parentType;
state.level = bufferedState.level;
state.prentLevel = bufferedState.prentLevel;
state.env = bufferedState.env;
return true;
};
exports.Lists = Lists;
//# sourceMappingURL=latex-list-env-block.js.map