fix(mermaid): parse compact multidirectional labels (#131)

* fix(mermaid): parse compact multidirectional labels

* fix(mermaid): preserve source ids ending in markers

* chore: bump plugin version to 2.6.2

* chore: bump plugin version to 2.6.4

* fix: preserve single-character Mermaid source IDs

* fix: preserve chained Mermaid marker IDs

---------

Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
This commit is contained in:
CHACON MONDRAGON
2026-08-21 14:31:30 -05:00
committed by GitHub
co-authored by Cathryn Lavery
parent ddaeae936e
commit c79585edb4
5 changed files with 101 additions and 16 deletions
@@ -605,21 +605,39 @@ def _edge_operators(text: str) -> list[_Operator]:
# Labeled links carry the label between the opening and closing operator:
# `A-- text -->B`, `A-. retry .-> B`, `A== critical ==> B`, and the
# undirected forms of each. The compact form drops the spaces —
# `B--yes-->C` — so its label may not contain whitespace, and the operator
# characters themselves may not open one (keeping `A----->B` unlabeled and
# `A --o B --> C` two separate links).
# `B--yes-->C` — and may retain a left arrow/circle/cross marker, as in
# `A<--yes-->B` or `A o--yes--o B`. Its label may not contain whitespace,
# and the operator characters themselves may not open one (keeping
# `A----->B` unlabeled and `A --o B --> C` two separate links).
text_edge = re.compile(
r"(?:--|-\.|==)"
r"(?:\s+(.+?)\s+|(?![-=.\s])([^\s|<>]+?))"
r"(\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
r"(?P<opening>"
r"<(?:--|-\.|==)"
r"|(?<![\w.:-])[xo](?:--|-\.|==)"
r"|(?:--|-\.|==)"
r")"
r"(?:\s+(?P<spaced>.+?)\s+|(?![-=.\s])(?P<compact>[^\s|<>]+?))"
r"(?P<closing>\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
)
trailing_operator = re.compile(
r"(?:\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
r"(?:\|[^|\n]*\|)?\s*$"
)
for match in text_edge.finditer(mask):
token = match.group(3)
label_group = 1 if match.group(1) is not None else 2
opening = match.group("opening")
operator_start = match.start()
if opening.startswith(("x", "o")):
prefix = mask[:operator_start]
if not prefix.strip() or trailing_operator.search(prefix):
# Here x/o is the endpoint before a regular opening operator,
# not a left marker: `x--yes-->B` or `A-->x--go-->B`.
operator_start += 1
opening = opening[1:]
token = opening + match.group("closing")
label_group = "spaced" if match.group("spaced") is not None else "compact"
style, arrowhead, bidirectional, undirected = _operator_style(token)
operators.append(
_Operator(
match.start(),
operator_start,
match.end(),
clean_label(text[match.start(label_group) : match.end(label_group)]),
style,
@@ -628,7 +646,7 @@ def _edge_operators(text: str) -> list[_Operator]:
undirected,
)
)
occupied.append((match.start(), match.end()))
occupied.append((operator_start, match.end()))
pattern = re.compile(
r"[xo][-=.]+[xo]|<[-=.]+>|-+\.-+>|=+>|-+(?:>|x|o)|-+\.-+|={3,}|-{3,}"