"""Process definition lists."""
from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock
from mdit_py_plugins.utils import is_code_block
[docs]def deflist_plugin(md: MarkdownIt) -> None:
"""Plugin ported from
`markdown-it-deflist <https://github.com/markdown-it/markdown-it-deflist>`__.
The syntax is based on
`pandoc definition lists <http://johnmacfarlane.net/pandoc/README.html#definition-lists>`__:
.. code-block:: md
Term 1
: Definition 1 long form
second paragraph
Term 2 with *inline markup*
~ Definition 2a compact style
~ Definition 2b
"""
def skipMarker(state: StateBlock, line: int) -> int:
"""Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""
start = state.bMarks[line] + state.tShift[line]
maximum = state.eMarks[line]
if start >= maximum:
return -1
# Check bullet
marker = state.src[start]
start += 1
if marker != "~" and marker != ":":
return -1
pos = state.skipSpaces(start)
# require space after ":"
if start == pos:
return -1
# no empty definitions, e.g. " : "
if pos >= maximum:
return -1
return start
def markTightParagraphs(state: StateBlock, idx: int) -> None:
level = state.level + 2
i = idx + 2
l2 = len(state.tokens) - 2
while i < l2:
if (
state.tokens[i].level == level
and state.tokens[i].type == "paragraph_open"
):
state.tokens[i + 2].hidden = True
state.tokens[i].hidden = True
i += 2
i += 1
def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:
if is_code_block(state, startLine):
return False
if silent:
# quirk: validation mode validates a dd block only, not a whole deflist
if state.ddIndent < 0:
return False
return skipMarker(state, startLine) >= 0
nextLine = startLine + 1
if nextLine >= endLine:
return False
if state.isEmpty(nextLine):
nextLine += 1
if nextLine >= endLine:
return False
if state.sCount[nextLine] < state.blkIndent:
return False
contentStart = skipMarker(state, nextLine)
if contentStart < 0:
return False
# Start list
listTokIdx = len(state.tokens)
tight = True
token = state.push("dl_open", "dl", 1)
token.map = listLines = [startLine, 0]
# Iterate list items
dtLine = startLine
ddLine = nextLine
# One definition list can contain multiple DTs,
# and one DT can be followed by multiple DDs.
#
# Thus, there is two loops here, and label is
# needed to break out of the second one
#
break_outer = False
while True:
prevEmptyEnd = False
token = state.push("dt_open", "dt", 1)
token.map = [dtLine, dtLine]
token = state.push("inline", "", 0)
token.map = [dtLine, dtLine]
token.content = state.getLines(
dtLine, dtLine + 1, state.blkIndent, False
).strip()
token.children = []
token = state.push("dt_close", "dt", -1)
while True:
token = state.push("dd_open", "dd", 1)
token.map = itemLines = [nextLine, 0]
pos = contentStart
maximum = state.eMarks[ddLine]
offset = (
state.sCount[ddLine]
+ contentStart
- (state.bMarks[ddLine] + state.tShift[ddLine])
)
while pos < maximum:
if state.src[pos] == "\t":
offset += 4 - offset % 4
elif state.src[pos] == " ":
offset += 1
else:
break
pos += 1
contentStart = pos
oldTight = state.tight
oldDDIndent = state.ddIndent
oldIndent = state.blkIndent
oldTShift = state.tShift[ddLine]
oldSCount = state.sCount[ddLine]
oldParentType = state.parentType
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
state.sCount[ddLine] = offset
state.tight = True
state.parentType = "deflist"
state.md.block.tokenize(state, ddLine, endLine)
# If any of list item is tight, mark list as tight
if not state.tight or prevEmptyEnd:
tight = False
# Item become loose if finish with empty line,
# but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 and state.isEmpty(
state.line - 1
)
state.tShift[ddLine] = oldTShift
state.sCount[ddLine] = oldSCount
state.tight = oldTight
state.parentType = oldParentType
state.blkIndent = oldIndent
state.ddIndent = oldDDIndent
token = state.push("dd_close", "dd", -1)
itemLines[1] = nextLine = state.line
if nextLine >= endLine:
break_outer = True
break
if state.sCount[nextLine] < state.blkIndent:
break_outer = True
break
contentStart = skipMarker(state, nextLine)
if contentStart < 0:
break
ddLine = nextLine
# go to the next loop iteration:
# insert DD tag and repeat checking
if break_outer:
break_outer = False
break
if nextLine >= endLine:
break
dtLine = nextLine
if state.isEmpty(dtLine):
break
if state.sCount[dtLine] < state.blkIndent:
break
ddLine = dtLine + 1
if ddLine >= endLine:
break
if state.isEmpty(ddLine):
ddLine += 1
if ddLine >= endLine:
break
if state.sCount[ddLine] < state.blkIndent:
break
contentStart = skipMarker(state, ddLine)
if contentStart < 0:
break
# go to the next loop iteration:
# insert DT and DD tags and repeat checking
# Finalise list
token = state.push("dl_close", "dl", -1)
listLines[1] = nextLine
state.line = nextLine
# mark paragraphs tight if needed
if tight:
markTightParagraphs(state, listTokIdx)
return True
md.block.ruler.before(
"paragraph",
"deflist",
deflist,
{"alt": ["paragraph", "reference", "blockquote"]},
)