fix(spell): close a Markdown code fence only on a matching fence (#986)

Any ``` line closed an open fence, so a ```` block showing a ``` block ended early and the inner code was spell checked as prose. Fences now close as in CommonMark: the same character, at least as long as the opening fence, and no info string.

Co-authored-by: fhrmrk <[email protected]>
Цей коміт міститься в:
Park Jong Hyun authored and GitHub committed 2026-11-03 19:29:16 +08:00
1 джерело bdeca5803b
коміт 4b1614af1f
1 file changed
+18 -3
+18 -3
Переглянути файл
@@ -422,15 +422,22 @@ fn capitalize(s: &str) -> String {
/// blocks and inline code spans.
pub fn prose_regions(text: &str) -> Vec<Region> {
let mut out = Vec::new();
let mut fence: Option<&str> = None;
// The open fence: its character and length.
let mut fence: Option<(char, usize)> = None;
let mut offset = 0;
for line in text.split_inclusive('\n') {
let start = offset;
offset += line.len();
let trimmed = line.trim_start();
let marker = ["```", "~~~"].into_iter().find(|m| trimmed.starts_with(m));
let marker = ['`', '~'].into_iter().find_map(|c| {
let n = trimmed.len() - trimmed.trim_start_matches(c).len();
(n >= 3).then_some((c, n))
});
match (fence, marker) {
(Some(open), Some(m)) if m == open => {
// CommonMark: a closing fence uses the same character, is at least as
// long as the opening one and has no info string, so a ```` fence can
// show a ``` block.
(Some((open, open_len)), Some((c, n))) if c == open && n >= open_len && trimmed[n..].trim().is_empty() => {
fence = None;
continue;
}
@@ -721,6 +728,14 @@ mod tests {
assert_eq!(spans, ["Some prose ", " here\n", "more\n"]);
}
#[test]
fn markdown_fences_close_on_a_matching_fence_only() {
let text = "Example:\n````markdown\n```rust\nfn wrng() {}\n```\n````\nmore\n~~~\n```\nnot clsed\n~~~~\nend\n";
let regions = prose_regions(text);
let spans: Vec<_> = regions.iter().map(|r| &text[r.start..r.end]).collect();
assert_eq!(spans, ["Example:\n", "more\n", "end\n"]);
}
#[test]
fn cancellation() {
let s = Speller::default();