diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9e0162bfb..260552393 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Changed +* Avoided a per-character `Seq.windowed`/`String()` allocation in `StartsWithNTimesTrimIgnoreStartWhitespace`, the active pattern used to count repeated fence characters (`` ` `` / `~`) when parsing code fences and headers. The count is now computed with `String.CompareOrdinal` over string offsets, with no intermediate substring allocations. Behavior is unchanged. + ## [23.0.0-alpha.8] - 2026-09-18 ### Added diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs index 27ec806cf..af4d7165c 100644 --- a/src/Common/StringParsing.fs +++ b/src/Common/StringParsing.fs @@ -232,11 +232,23 @@ module StringPosition = if String.IsNullOrWhiteSpace(beforeStart) then let startAndRest = text.Substring(beforeStart.Length) + // Count matches of `start` at consecutive offsets 0, 1, 2, ... in `startAndRest` + // (same semantics as the previous Seq.windowed-based implementation), without + // allocating a substring per offset. let startNum = - Seq.windowed start.Length startAndRest - |> Seq.map (fun chars -> System.String(chars)) - |> Seq.takeWhile ((=) start) - |> Seq.length + let mutable count = 0 + let mutable keepGoing = true + + while keepGoing do + if + count + start.Length <= startAndRest.Length + && String.CompareOrdinal(startAndRest, count, start, 0, start.Length) = 0 + then + count <- count + 1 + else + keepGoing <- false + + count Some( startNum,