|
54 | 54 | # whose total width is the actual width of the contents;
|
55 | 55 | # thus they can be used as a component in a containing expression
|
56 | 56 |
|
57 |
| -RE_OPEN = re.compile(r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}") |
| 57 | +RE_OPEN = r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}" |
58 | 58 |
|
59 | 59 |
|
60 | 60 | def amsmath_plugin(
|
@@ -95,47 +95,60 @@ def render_amsmath_block(
|
95 | 95 | md.add_render_rule("amsmath", render_amsmath_block)
|
96 | 96 |
|
97 | 97 |
|
98 |
| -def match_environment(string: str) -> None | tuple[str, str, int]: |
99 |
| - match_open = RE_OPEN.match(string) |
100 |
| - if not match_open: |
101 |
| - return None |
102 |
| - environment = match_open.group(1) |
103 |
| - numbered = match_open.group(2) |
104 |
| - match_close = re.search( |
105 |
| - r"\\end\{" + environment + numbered.replace("*", r"\*") + "\\}", string |
106 |
| - ) |
107 |
| - if not match_close: |
108 |
| - return None |
109 |
| - return (environment, numbered, match_close.end()) |
110 |
| - |
111 |
| - |
112 | 98 | def amsmath_block(
|
113 | 99 | state: StateBlock, startLine: int, endLine: int, silent: bool
|
114 | 100 | ) -> bool:
|
| 101 | + # note the code principally follows the logic in markdown_it/rules_block/fence.py, |
| 102 | + # except that: |
| 103 | + # (a) it allows for closing tag on same line as opening tag |
| 104 | + # (b) it does not allow for opening tag without closing tag (i.e. no auto-closing) |
| 105 | + |
115 | 106 | if is_code_block(state, startLine):
|
116 | 107 | return False
|
117 | 108 |
|
118 |
| - begin = state.bMarks[startLine] + state.tShift[startLine] |
| 109 | + # does the first line contain the beginning of an amsmath environment |
| 110 | + first_start = state.bMarks[startLine] + state.tShift[startLine] |
| 111 | + first_end = state.eMarks[startLine] |
| 112 | + first_text = state.src[first_start:first_end] |
119 | 113 |
|
120 |
| - outcome = match_environment(state.src[begin:]) |
121 |
| - if not outcome: |
| 114 | + if not (match_open := re.match(RE_OPEN, first_text)): |
122 | 115 | return False
|
123 |
| - environment, numbered, endpos = outcome |
124 |
| - endpos += begin |
125 |
| - |
126 |
| - line = startLine |
127 |
| - while line < endLine: |
128 |
| - if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]: |
129 |
| - # line for end of block math found ... |
130 |
| - state.line = line + 1 |
| 116 | + |
| 117 | + # construct the closing tag |
| 118 | + environment = match_open.group(1) |
| 119 | + numbered = match_open.group(2) |
| 120 | + closing = rf"\end{{{match_open.group(1)}{match_open.group(2)}}}" |
| 121 | + |
| 122 | + # start looking for the closing tag, including the current line |
| 123 | + nextLine = startLine - 1 |
| 124 | + |
| 125 | + while True: |
| 126 | + nextLine += 1 |
| 127 | + if nextLine >= endLine: |
| 128 | + # reached the end of the block without finding the closing tag |
| 129 | + return False |
| 130 | + |
| 131 | + next_start = state.bMarks[nextLine] + state.tShift[nextLine] |
| 132 | + next_end = state.eMarks[nextLine] |
| 133 | + if next_start < first_end and state.sCount[nextLine] < state.blkIndent: |
| 134 | + # non-empty line with negative indent should stop the list: |
| 135 | + # - \begin{align} |
| 136 | + # test |
| 137 | + return False |
| 138 | + |
| 139 | + if state.src[next_start:next_end].rstrip().endswith(closing): |
| 140 | + # found the closing tag |
131 | 141 | break
|
132 |
| - line += 1 |
| 142 | + |
| 143 | + state.line = nextLine + 1 |
133 | 144 |
|
134 | 145 | if not silent:
|
135 | 146 | token = state.push("amsmath", "math", 0)
|
136 | 147 | token.block = True
|
137 |
| - token.content = state.src[begin:endpos] |
| 148 | + token.content = state.getLines( |
| 149 | + startLine, state.line, state.sCount[startLine], False |
| 150 | + ) |
138 | 151 | token.meta = {"environment": environment, "numbered": numbered}
|
139 |
| - token.map = [startLine, line] |
| 152 | + token.map = [startLine, nextLine] |
140 | 153 |
|
141 | 154 | return True
|
0 commit comments