Skip to content

Commit 113c54a

Browse files
fix: use realistic source spans for Markdown (#878)
This fixes a panic in some clients of the Markdown support that used it to add blocks to a document after #771.
1 parent 542cc09 commit 113c54a

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

src/tests/Tests/VersoManual/Markdown.lean

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,46 @@ def testAddPartFromMarkdown (input : String) : Elab.TermElabM String := do
4343
let (_, _, part) ← addParts.run ⟨Syntax.node .none identKind #[], mkConst ``Manual, .always, .none⟩ default default
4444
part.partContext.priorParts.toList.map displayPartStructure |> String.join |> pure
4545

46+
/--
47+
Every part produced from Markdown headers must have range and selection syntax whose recovered
48+
ranges satisfy the TOC invariant: the selection range lies within `[rangeStart, endPos]`. Violating
49+
it (position-less syntax, or an `endPos` of zero) panics in `requireValidTOCRanges` during the
50+
document-symbol/folding conversion.
51+
-/
52+
partial def partRangesValid : FinishedPart → Bool
53+
| .mk rangeStx selectionStx _ _ _ _ subParts endPos =>
54+
match rangeStx.getRange?, selectionStx.getRange? with
55+
| some r, some sel => (r.start ≤ sel.start) && (sel.stop ≤ endPos) && subParts.all partRangesValid
56+
| _, _ => false
57+
| .included _ => true
58+
59+
open PartElabM in
60+
/--
61+
Parses Markdown under a positioned reference and closes the document at that reference's end, as a
62+
real document's Markdown block elaborator would, then reports whether every resulting part has a
63+
valid TOC range.
64+
-/
65+
def markdownPartRangesValid (input : String) : Elab.TermElabM Bool := do
66+
let some parsed := MD4Lean.parse input
67+
| throwError m!"Couldn't parse markdown {input}"
68+
let ref := Syntax.node (.synthetic ⟨0⟩ ⟨100⟩) identKind #[]
69+
withRef ref do
70+
let addParts : PartElabM Unit := do
71+
let mut levels := []
72+
for block in parsed.blocks do
73+
levels ← addPartFromMarkdown block levels
74+
closePartsUntil 0 (ref.getTailPos?.getD default)
75+
let (_, _, part) ← addParts.run ⟨Syntax.node .none identKind #[], mkConst ``Manual, .always, .none⟩ default default
76+
return part.partContext.priorParts.all partRangesValid
77+
78+
/-- info: true -/
79+
#guard_msgs in
80+
#eval markdownPartRangesValid r#"
81+
# Acknowledgements
82+
## Contributors
83+
# Another Section
84+
"#
85+
4686
/--
4787
info:
4888
# header1

src/verso-manual/VersoManual/Markdown.lean

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,11 @@ private partial def closeMarkdownSections {m} [Monad m]
284284
| [] => pure ()
285285
| docLevel :: more =>
286286
if docLevel ≥ level then
287-
-- `default` here because the Markdown parser provides no source position
288-
let some ctxt' := (← getThe PartElabM.State).partContext.close default
287+
-- Markdown headers carry no source extent of their own, so end the section at the end of the
288+
-- current reference. This keeps each part's range valid (the selection stays within
289+
-- `[rangeStart, endPos]`) for the TOC range conversion.
290+
let endPos := (← getRef).getTailPos?.getD default
291+
let some ctxt' := (← getThe PartElabM.State).partContext.close endPos
289292
| throwError m!"Failed to close verso part corresponding to markdown section: no parts left"
290293
modifyThe PartElabM.State fun st => {st with partContext := ctxt'}
291294
modifyThe MDState ({· with inHeaders := more})
@@ -313,7 +316,9 @@ private partial def addPartFromMarkdownAux {m} [Monad m]
313316
| .ok t => pure t
314317
| .error e => throwError m!"Unsupported Markdown in header:\n{e}"
315318
let titleText := titleTexts.foldl (· ++ ·) ""
316-
let titleSyntax := quote (k := `str) titleText
319+
-- Markdown headers have no source syntax of their own, so anchor TOC ranges to the current
320+
-- reference, which has a real source position.
321+
let titleSyntax ← getRef
317322
startMarkdownSection level {
318323
rangeSyntax := titleSyntax
319324
selectionSyntax := titleSyntax

0 commit comments

Comments
 (0)