Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [Unreleased]

### Changed
* Avoid recomputing `List.length` on the shrinking remainder for every scanned character when splitting a Markdown pipe-table row into cells (`pipeTableFindSplits` in `MarkdownTableParser.fs`). The scan now tracks the number of consumed characters incrementally, turning an O(nΒ²) split into an O(n) one for rows with many delimiters, with no behavior change.

## [23.0.0-alpha.2] - 2026-09-10

### Fixed
Expand Down
34 changes: 21 additions & 13 deletions src/FSharp.Formatting.Markdown/MarkdownTableParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,34 @@ let rec pipeTableFindSplits (delim: char array) (line: char list) =
let cLstToStr (x: char list) =
x |> Array.ofList |> System.String.Concat

// Scans the line and, on success, returns the number of characters consumed up to and
// including the found delimiter, together with the remaining list. Tracking the consumed
// count incrementally (rather than recomputing `List.length` over the shrinking remainder
// on every character) keeps this scan linear instead of quadratic in the row length.
let rec ptfs delim line =
match line with
| DelimitedLatexDisplayMath [ '$'; '$' ] (_body, rest) -> ptfs delim rest
| DelimitedLatexInlineMath [ '$' ] (_body, rest) -> ptfs delim rest
| List.DelimitedWith [ '`'; ' ' ] [ ' '; '`' ] (_body, rest, _s, _e) -> ptfs delim rest
| List.DelimitedNTimes '`' (_body, rest, _s, _e) -> ptfs delim rest
| x :: rest when Array.exists ((=) x) delim -> Some rest
| '\\' :: _ :: rest
| _ :: rest -> ptfs delim rest
| DelimitedLatexDisplayMath [ '$'; '$' ] (_body, rest)
| DelimitedLatexInlineMath [ '$' ] (_body, rest)
| List.DelimitedWith [ '`'; ' ' ] [ ' '; '`' ] (_body, rest, _, _)
| List.DelimitedNTimes '`' (_body, rest, _, _) ->
let consumedHere = List.length line - List.length rest

ptfs delim rest
|> Option.map (fun (count, remainder) -> (count + consumedHere, remainder))
| x :: rest when Array.exists ((=) x) delim -> Some(1, rest)
| '\\' :: _ :: rest -> ptfs delim rest |> Option.map (fun (count, remainder) -> (count + 2, remainder))
| _ :: rest -> ptfs delim rest |> Option.map (fun (count, remainder) -> (count + 1, remainder))
| [] -> None

let rest = ptfs delim line
let result = ptfs delim line

match rest with
match result with
| None -> [ cLstToStr line ]
| Some _x when List.isEmpty line -> [ "" ]
| Some x ->
let chunkSize = List.length line - List.length x - 1
| Some _ when List.isEmpty line -> [ "" ]
| Some(count, x) ->
let chunkSize = count - 1

cLstToStr (Seq.take chunkSize line |> Seq.toList) :: pipeTableFindSplits delim x
cLstToStr (List.truncate chunkSize line) :: pipeTableFindSplits delim x



Expand Down
29 changes: 29 additions & 0 deletions tests/FSharp.Markdown.Tests/Markdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,35 @@ let ``Transform tables with delimiters in code or math correctly`` () =

Markdown.ToHtml doc |> shouldEqualNoWhiteSpace expected

[<Test>]
let ``Transform tables with escaped pipe characters correctly`` () =
let doc =
"""| a | b |
|---|---|
| 1\|2 | 3 |
"""

let expected =
"""<table>
<thead>
<tr class="header">
<th><p>a</p></th>
<th><p>b</p></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><p>1\|2</p></td>
<td><p>3</p></td>
</tr>
</tbody>
</table>

"""
|> properNewLines

Markdown.ToHtml doc |> shouldEqualNoWhiteSpace expected

[<Test>]
let ``Parse empty blockquote followed by content`` () =
let doc =
Expand Down
Loading