Skip to content

Commit 4340227

Browse files
authored
fix: Off-by-one symbol range for headings ending with supplementary Unicode characters (#455)
Use Char.IsHighSurrogate instead of Char.IsSurrogate when computing the end offset in sourceSpanToRange. When a span ends on a low surrogate (the second code unit of a surrogate pair), adding 2 produces an out-of-bounds position. Adding 1 is correct since the position already points to the last code unit. Fixes #453
1 parent 8249efa commit 4340227

2 files changed

Lines changed: 24 additions & 3 deletions

File tree

Marksman/Parser.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ module Markdown =
210210
{ Start = start; End = start }
211211
else
212212
let endInclusive = text.lineMap.FindPosition(span.End)
213-
let endOffset = if Char.IsSurrogate(text.content, span.End) then 2 else 1
213+
214+
let endOffset =
215+
if Char.IsHighSurrogate(text.content, span.End) then 2 else 1
214216

215217
{
216218
Start = start

Tests/ParserTests.fs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,14 +466,31 @@ module RegressionTests =
466466
let actual = scrapeString content
467467
checkInlineSnapshot actual []
468468

469+
[<Fact>]
470+
let no453 () =
471+
// Off-by-one in heading range when heading ends with emoji (surrogate pair)
472+
let content = "## 45\n## 🚀"
473+
let actual = scrapeString content
474+
475+
checkInlineSnapshot actual [
476+
"H2: range=(0,0)-(0,5); scope=(0,0)-(1,0)"
477+
" text=`## 45`"
478+
" title=`45` @ (0,3)-(0,5)"
479+
"H2: range=(1,0)-(1,5); scope=(1,0)-(2,0)"
480+
" text=`## 🚀`"
481+
" title=`🚀` @ (1,3)-(1,5)"
482+
]
483+
469484
module MathBlockTests =
470485
[<Fact>]
471486
let math_block_should_not_parse_wikilinks () =
472-
let content = """$$
487+
let content =
488+
"""$$
473489
\begin{verbatim}
474490
[[nodiscard]]
475491
\end{verbatim}
476492
$$"""
493+
477494
let actual = scrapeString content
478495
// Math block should not produce any wikilink elements
479496
checkInlineSnapshot actual []
@@ -487,11 +504,13 @@ $$"""
487504

488505
[<Fact>]
489506
let math_and_regular_wikilink () =
490-
let content = """$$
507+
let content =
508+
"""$$
491509
[[in-math]]
492510
$$
493511
494512
Regular [[valid-link]]"""
513+
495514
let actual = scrapeString content
496515
// Only the regular wikilink should be detected, not the one in math block
497516
checkInlineSnapshot actual [

0 commit comments

Comments
 (0)