From 1a3186aa252bb91017e3da3e23e749c9b70d45dc Mon Sep 17 00:00:00 2001
From: Avi Avni
Date: Sun, 19 Jun 2016 06:16:17 +0300
Subject: [PATCH 1/6] add range to MarkdownParagraph and MarkdownSpan
---
src/Common/StringParsing.fs | 106 +++++++++---
src/FSharp.CodeFormat/CommentFilter.fs | 10 +-
src/FSharp.Literate/Document.fs | 2 +-
src/FSharp.Literate/Evaluator.fs | 8 +-
src/FSharp.Literate/Formatting.fs | 10 +-
src/FSharp.Literate/Main.fs | 4 +-
src/FSharp.Literate/ParseScript.fs | 24 +--
src/FSharp.Literate/Transformations.fs | 54 +++----
src/FSharp.Markdown/HtmlFormatting.fs | 64 ++++----
src/FSharp.Markdown/LatexFormatting.fs | 48 +++---
src/FSharp.Markdown/Main.fs | 10 +-
src/FSharp.Markdown/Markdown.fs | 96 +++++------
src/FSharp.Markdown/MarkdownParser.fs | 195 ++++++++++++-----------
src/FSharp.MetadataFormat/Main.fs | 14 +-
tests/FSharp.Literate.Tests/EvalTests.fs | 16 +-
tests/FSharp.Literate.Tests/Tests.fs | 26 +--
tests/FSharp.Markdown.Tests/Markdown.fs | 36 ++---
17 files changed, 394 insertions(+), 329 deletions(-)
diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs
index 0f376562a..8d2ce0d8c 100644
--- a/src/Common/StringParsing.fs
+++ b/src/Common/StringParsing.fs
@@ -14,44 +14,57 @@ open FSharp.Collections
module String =
/// Matches when a string is a whitespace or null
- let (|WhiteSpace|_|) s =
+ let (|WhiteSpaceS|_|) (s) =
+ if String.IsNullOrWhiteSpace(s) then Some() else None
+
+ /// Matches when a string is a whitespace or null
+ let (|WhiteSpace|_|) (s, n: int) =
if String.IsNullOrWhiteSpace(s) then Some() else None
/// Matches when a string does starts with non-whitespace
- let (|Unindented|_|) (s:string) =
+ let (|Unindented|_|) (s:string, n:int) =
if not (String.IsNullOrWhiteSpace(s)) && s.TrimStart() = s then Some() else None
/// Returns a string trimmed from both start and end
- let (|TrimBoth|) (text:string) = text.Trim()
+ let (|TrimBothS|) (text:string) = text.Trim()
+ /// Returns a string trimmed from both start and end
+ let (|TrimBoth|) (text:string, n:int) = (text.Trim(), n)
/// Returns a string trimmed from the end
- let (|TrimEnd|) (text:string) = text.TrimEnd()
+ let (|TrimEnd|) (text:string, n:int) = (text.TrimEnd(), n)
/// Returns a string trimmed from the start
- let (|TrimStart|) (text:string) = text.TrimStart()
+ let (|TrimStart|) (text:string, n:int) = (text.TrimStart(), n)
/// Retrusn a string trimmed from the end using characters given as a parameter
- let (|TrimEndUsing|) chars (text:string) = text.TrimEnd(Array.ofSeq chars)
+ let (|TrimEndUsing|) chars (text:string, n:int) = text.TrimEnd(Array.ofSeq chars)
/// Returns a string trimmed from the start together with
/// the number of skipped whitespace characters
- let (|TrimStartAndCount|) (text:string) =
+ let (|TrimStartAndCount|) (text:string, n:int) =
let trimmed = text.TrimStart([|' '; '\t'|])
let len = text.Length - trimmed.Length
- len, text.Substring(0, len).Replace("\t", " ").Length, trimmed
+ len, text.Substring(0, len).Replace("\t", " ").Length, (trimmed, n)
/// Matches when a string starts with any of the specified sub-strings
- let (|StartsWithAny|_|) (starts:seq) (text:string) =
+ let (|StartsWithAny|_|) (starts:seq) (text:string, n:int) =
if starts |> Seq.exists (text.StartsWith) then Some() else None
/// Matches when a string starts with the specified sub-string
- let (|StartsWith|_|) (start:string) (text:string) =
+ let (|StartsWithS|_|) (start:string) (text:string) =
if text.StartsWith(start) then Some(text.Substring(start.Length)) else None
/// Matches when a string starts with the specified sub-string
+ let (|StartsWith|_|) (start:string) (text:string, n:int) =
+ if text.StartsWith(start) then Some(text.Substring(start.Length), n) else None
+ /// Matches when a string starts with the specified sub-string
/// The matched string is trimmed from all whitespace.
- let (|StartsWithTrim|_|) (start:string) (text:string) =
+ let (|StartsWithTrimS|_|) (start:string) (text:string) =
if text.StartsWith(start) then Some(text.Substring(start.Length).Trim()) else None
+ /// Matches when a string starts with the specified sub-string
+ /// The matched string is trimmed from all whitespace.
+ let (|StartsWithTrim|_|) (start:string) (text:string, n:int) =
+ if text.StartsWith(start) then Some(text.Substring(start.Length).Trim(), n) else None
/// Matches when a string starts with the specified sub-string (ignoring whitespace at the start)
/// The matched string is trimmed from all whitespace.
- let (|StartsWithNTimesTrimIgnoreStartWhitespace|_|) (start:string) (text:string) =
+ let (|StartsWithNTimesTrimIgnoreStartWhitespace|_|) (start:string) (text:string, n:int) =
if text.Contains(start) then
let beforeStart = text.Substring(0, text.IndexOf(start))
if String.IsNullOrWhiteSpace (beforeStart) then
@@ -67,12 +80,26 @@ module String =
/// Matches when a string starts with the given value and ends
/// with a given value (and returns the rest of it)
- let (|StartsAndEndsWith|_|) (starts, ends) (s:string) =
+ let (|StartsAndEndsWithS|_|) (starts, ends) (s:string) =
if s.StartsWith(starts) && s.EndsWith(ends) &&
s.Length >= starts.Length + ends.Length then
Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length))
else None
+ /// Matches when a string starts with the given value and ends
+ /// with a given value (and returns the rest of it)
+ let (|StartsAndEndsWith|_|) (starts, ends) (s:string, n:int) =
+ if s.StartsWith(starts) && s.EndsWith(ends) &&
+ s.Length >= starts.Length + ends.Length then
+ Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length), n)
+ else None
+
+ /// Matches when a string starts with the given value and ends
+ /// with a given value (and returns trimmed body)
+ let (|StartsAndEndsWithTrimS|_|) args = function
+ | StartsAndEndsWithS args (TrimBothS res) -> Some res
+ | _ -> None
+
/// Matches when a string starts with the given value and ends
/// with a given value (and returns trimmed body)
let (|StartsAndEndsWithTrim|_|) args = function
@@ -85,7 +112,7 @@ module String =
///
/// let (StartsWithRepeated "/\" (2, " abc")) = "/\/\ abc"
///
- let (|StartsWithRepeated|_|) (repeated:string) (text:string) =
+ let (|StartsWithRepeated|_|) (repeated:string) (text:string, ln:int) =
let rec loop i =
if i = text.Length then i
elif text.[i] <> repeated.[i % repeated.Length] then i
@@ -93,7 +120,7 @@ module String =
let n = loop 0
if n = 0 || n % repeated.Length <> 0 then None
- else Some(n/repeated.Length, text.Substring(n, text.Length - n))
+ else Some(n/repeated.Length, (text.Substring(n, text.Length - n), ln))
/// Ignores everything until a end-line character is detected, returns the remaining string.
let (|SkipSingleLine|) (text:string) =
@@ -114,12 +141,11 @@ module String =
FSharp.Formatting.Common.Log.warnf "could not skip a line of %s, because no line-ending character was found!" text
result
-
/// Matches when a string starts with a sub-string wrapped using the
/// opening and closing sub-string specified in the parameter.
/// For example "[aa]bc" is wrapped in [ and ] pair. Returns the wrapped
/// text together with the rest.
- let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string) =
+ let (|StartsWithWrappedS|_|) (starts:string, ends:string) (text:string) =
if text.StartsWith(starts) then
let id = text.IndexOf(ends, starts.Length)
if id >= 0 then
@@ -129,10 +155,24 @@ module String =
else None
else None
+ /// Matches when a string starts with a sub-string wrapped using the
+ /// opening and closing sub-string specified in the parameter.
+ /// For example "[aa]bc" is wrapped in [ and ] pair. Returns the wrapped
+ /// text together with the rest.
+ let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string, n:int) =
+ if text.StartsWith(starts) then
+ let id = text.IndexOf(ends, starts.Length)
+ if id >= 0 then
+ let wrapped = text.Substring(starts.Length, id - starts.Length)
+ let rest = text.Substring(id + ends.Length, text.Length - id - ends.Length)
+ Some(wrapped, (rest, n))
+ else None
+ else None
+
/// Matches when a string consists of some number of
/// complete repetitions of a specified sub-string.
- let (|EqualsRepeated|_|) repeated = function
- | StartsWithRepeated repeated (n, "") -> Some()
+ let (|EqualsRepeated|_|) (repeated, n:int) = function
+ | StartsWithRepeated repeated (n, ("", _)) -> Some()
| _ -> None
/// Given a list of lines indented with certan number of whitespace
@@ -197,8 +237,8 @@ module Lines =
/// Removes blank lines from the start and the end of a list
let (|TrimBlank|) lines =
lines
- |> List.skipWhile String.IsNullOrWhiteSpace |> List.rev
- |> List.skipWhile String.IsNullOrWhiteSpace |> List.rev
+ |> List.skipWhile (fun (s, n) -> String.IsNullOrWhiteSpace s) |> List.rev
+ |> List.skipWhile (fun (s, n) -> String.IsNullOrWhiteSpace s) |> List.rev
/// Matches when there are some lines at the beginning that are
/// either empty (or whitespace) or start with the specified string.
@@ -213,7 +253,7 @@ module Lines =
/// either empty (or whitespace) or start with at least 4 spaces (a tab counts as 4 spaces here).
/// Returns all such lines from the beginning until a different line and
/// the number of spaces the first line started with.
- let (|TakeCodeBlock|_|) (input:string list) =
+ let (|TakeCodeBlock|_|) (input:(string * int) list) =
let spaceNum = 4
//match input with
//| h :: _ ->
@@ -225,20 +265,20 @@ module Lines =
let normalized = s.Replace("\t", " ")
normalized.Length >= spaceNum &&
normalized.Substring(0, spaceNum) = System.String(' ', spaceNum)
- match List.partitionWhile (fun s ->
+ match List.partitionWhile (fun (s, n) ->
String.IsNullOrWhiteSpace s || startsWithSpaces s) input with
| matching, rest when matching <> [] && spaceNum >= 4 ->
Some(spaceNum, matching, rest)
| _ -> None
/// Removes whitespace lines from the beginning of the list
- let (|TrimBlankStart|) = List.skipWhile (String.IsNullOrWhiteSpace)
+ let (|TrimBlankStart|) = List.skipWhile (fun (s:string, n:int) -> String.IsNullOrWhiteSpace s)
/// Trims all lines of the current paragraph
let (|TrimParagraphLines|) lines =
lines
// first remove all whitespace on the beginning of the line
- |> List.map (fun (s:string) -> s.TrimStart())
+ |> List.map (fun (s:string, n:int) -> s.TrimStart())
// Now remove all additional spaces at the end, but keep two spaces if existent
|> List.map (fun s ->
let endsWithTwoSpaces = s.EndsWith(" ")
@@ -258,7 +298,21 @@ open System.Collections.Generic
/// recognize `key1=value, key2=value` and also `key1:value, key2:value`
/// The key of the command should be identifier with just
/// characters in it - otherwise, the parsing fails.
-let (|ParseCommands|_|) (str:string) =
+let (|ParseCommandsS|_|) (str:string) =
+ let kvs =
+ [ for cmd in str.Split(',') do
+ let kv = cmd.Split([| '='; ':' |])
+ if kv.Length = 2 then yield kv.[0].Trim(), kv.[1].Trim()
+ elif kv.Length = 1 then yield kv.[0].Trim(), "" ]
+ let allKeysValid =
+ kvs |> Seq.forall (fst >> Seq.forall (fun c -> Char.IsLetter c || c = '_' || c = '-'))
+ if allKeysValid && kvs <> [] then Some(dict kvs) else None
+
+/// Utility for parsing commands. Commands can be used in different places. We
+/// recognize `key1=value, key2=value` and also `key1:value, key2:value`
+/// The key of the command should be identifier with just
+/// characters in it - otherwise, the parsing fails.
+let (|ParseCommands|_|) (str:string, n:int) =
let kvs =
[ for cmd in str.Split(',') do
let kv = cmd.Split([| '='; ':' |])
diff --git a/src/FSharp.CodeFormat/CommentFilter.fs b/src/FSharp.CodeFormat/CommentFilter.fs
index 279f5e176..8364862e2 100644
--- a/src/FSharp.CodeFormat/CommentFilter.fs
+++ b/src/FSharp.CodeFormat/CommentFilter.fs
@@ -49,12 +49,12 @@ let rec getSnippets (state:NamedSnippet option) (snippets:NamedSnippet list)
match source with
| [] -> snippets
| (line, tokens)::rest ->
- let text = lines.[line].Trim()
+ let text = lines.[line].Trim(), line
match state, text with
// We're not inside a snippet and we found a beginning of one
| None, String.StartsWithTrim "//" (String.StartsWithTrim "[snippet:" title) ->
- let title = title.Substring(0, title.IndexOf(']'))
+ let title = (fst title).Substring(0, (fst title).IndexOf(']'))
getSnippets (Some(title, [])) snippets rest lines
// Not inside a snippet and there is a usual line
| None, _ ->
@@ -92,7 +92,7 @@ let rec shrinkOmittedCode (text:StringBuilder) line content (source:Snippet) =
// Take the next line, merge comments and continue looking for end
| [], (line, content)::source ->
shrinkOmittedCode (text.Append("\n")) line (mergeComments content None []) source
- | (String.StartsAndEndsWithTrim ("(*", "*)") "[/omit]", tok)::rest, source
+ | (String.StartsAndEndsWithTrimS ("(*", "*)") "[/omit]", tok)::rest, source
when tok.TokenName = "COMMENT" ->
line, rest, source, text
| (str, tok)::rest, _ ->
@@ -105,13 +105,13 @@ let rec shrinkOmittedCode (text:StringBuilder) line content (source:Snippet) =
let rec shrinkLine line (content:SnippetLine) (source:Snippet) =
match content with
| [] -> [], source
- | (String.StartsAndEndsWithTrim ("(*", "*)") (String.StartsAndEndsWithTrim ("[omit:", "]") body), (tok:FSharpTokenInfo))::rest
+ | (String.StartsAndEndsWithTrimS ("(*", "*)") (String.StartsAndEndsWithTrimS ("[omit:", "]") body), (tok:FSharpTokenInfo))::rest
when tok.TokenName = "COMMENT" ->
let line, remcontent, source, text =
shrinkOmittedCode (StringBuilder()) line rest source
let line, source = shrinkLine line remcontent source
(body, { tok with TokenName = "OMIT" + (text.ToString()) })::line, source
- | (String.StartsWithTrim "//" (String.StartsAndEndsWith ("[fsi:", "]") fsi), (tok:FSharpTokenInfo))::rest ->
+ | (String.StartsWithTrimS "//" (String.StartsAndEndsWithS ("[fsi:", "]") fsi), (tok:FSharpTokenInfo))::rest ->
let line, source = shrinkLine line rest source
(fsi, { tok with TokenName = "FSI"})::line, source
| (str, tok)::rest ->
diff --git a/src/FSharp.Literate/Document.fs b/src/FSharp.Literate/Document.fs
index 0441373d4..e7bc9323b 100644
--- a/src/FSharp.Literate/Document.fs
+++ b/src/FSharp.Literate/Document.fs
@@ -112,4 +112,4 @@ type LiterateDocument(paragraphs, formattedTips, links, source, sourceFile, erro
/// Markdown documents.
module Matching =
let (|LiterateParagraph|_|) = function
- | EmbedParagraphs(:? LiterateParagraph as lp) -> Some lp | _ -> None
\ No newline at end of file
+ | EmbedParagraphs(:? LiterateParagraph as lp, _) -> Some lp | _ -> None
\ No newline at end of file
diff --git a/src/FSharp.Literate/Evaluator.fs b/src/FSharp.Literate/Evaluator.fs
index 27a87486f..2241114d5 100644
--- a/src/FSharp.Literate/Evaluator.fs
+++ b/src/FSharp.Literate/Evaluator.fs
@@ -111,7 +111,7 @@ type FsiEvaluator(?options:string[], ?fsiObj) =
/// Registered transformations for pretty printing values
/// (the default formats value as a string and emits single CodeBlock)
let mutable valueTransformations =
- [ (fun (o:obj, t:Type) ->Some([CodeBlock (sprintf "%A" o, "", "")]) ) ]
+ [ (fun (o:obj, t:Type) ->Some([CodeBlock (sprintf "%A" o, "", "", None)]) ) ]
/// Register a function that formats (some) values that are produced by the evaluator.
/// The specified function should return 'Some' when it knows how to format a value
@@ -130,12 +130,12 @@ type FsiEvaluator(?options:string[], ?fsiObj) =
match result :?> FsiEvaluationResult, kind with
| result, FsiEmbedKind.Output ->
let s = defaultArg result.Output "No output has been produced."
- [ CodeBlock(s.Trim(), "", "") ]
+ [ CodeBlock(s.Trim(), "", "", None) ]
| { ItValue = Some v }, FsiEmbedKind.ItValue
| { Result = Some v }, FsiEmbedKind.Value ->
valueTransformations |> Seq.pick (fun f -> lock lockObj (fun () -> f v))
- | _, FsiEmbedKind.ItValue -> [ CodeBlock ("No value has been returned", "", "") ]
- | _, FsiEmbedKind.Value -> [ CodeBlock ("No value has been returned", "", "") ]
+ | _, FsiEmbedKind.ItValue -> [ CodeBlock ("No value has been returned", "", "", None) ]
+ | _, FsiEmbedKind.Value -> [ CodeBlock ("No value has been returned", "", "", None) ]
/// Evaluates the given text in an fsi session and returns
/// an FsiEvaluationResult.
diff --git a/src/FSharp.Literate/Formatting.fs b/src/FSharp.Literate/Formatting.fs
index 9d02c7c23..93bbad5a3 100644
--- a/src/FSharp.Literate/Formatting.fs
+++ b/src/FSharp.Literate/Formatting.fs
@@ -27,8 +27,8 @@ module Formatting =
/// Try find first-level heading in the paragraph collection
let findHeadings paragraphs generateAnchors (outputKind:OutputKind) =
paragraphs |> Seq.tryPick (function
- | (Heading(1, text)) ->
- let doc = MarkdownDocument([Span(text)], dict [])
+ | Heading(1, text, r) ->
+ let doc = MarkdownDocument([Span(text, r)], dict [])
Some(format doc generateAnchors outputKind)
| _ -> None)
@@ -37,13 +37,13 @@ module Formatting =
let getSourceDocument (doc:LiterateDocument) =
match doc.Source with
| LiterateSource.Markdown text ->
- doc.With(paragraphs = [CodeBlock (text, "", "")])
+ doc.With(paragraphs = [CodeBlock (text, "", "", None)])
| LiterateSource.Script snippets ->
let paragraphs =
[ for Snippet(name, lines) in snippets do
if snippets.Length > 1 then
- yield Heading(3, [Literal name])
- yield EmbedParagraphs(FormattedCode(lines)) ]
+ yield Heading(3, [Literal(name, None)], None)
+ yield EmbedParagraphs(FormattedCode(lines), None) ]
doc.With(paragraphs = paragraphs)
// --------------------------------------------------------------------------------------
diff --git a/src/FSharp.Literate/Main.fs b/src/FSharp.Literate/Main.fs
index bc7b0cef0..37a7314ac 100644
--- a/src/FSharp.Literate/Main.fs
+++ b/src/FSharp.Literate/Main.fs
@@ -104,7 +104,7 @@ type Literate private () =
static member WriteHtml(doc:LiterateDocument, ?prefix, ?lineNumbers, ?generateAnchors) =
let ctx = formattingContext None (Some OutputKind.Html) prefix lineNumbers None generateAnchors None None
let doc = Transformations.replaceLiterateParagraphs ctx doc
- let doc = MarkdownDocument(doc.Paragraphs @ [InlineBlock doc.FormattedTips], doc.DefinedLinks)
+ let doc = MarkdownDocument(doc.Paragraphs @ [InlineBlock(doc.FormattedTips, None)], doc.DefinedLinks)
let sb = new System.Text.StringBuilder()
use wr = new StringWriter(sb)
Html.formatMarkdown wr ctx.GenerateHeaderAnchors Environment.NewLine true doc.DefinedLinks doc.Paragraphs
@@ -113,7 +113,7 @@ type Literate private () =
static member WriteHtml(doc:LiterateDocument, writer:TextWriter, ?prefix, ?lineNumbers, ?generateAnchors) =
let ctx = formattingContext None (Some OutputKind.Html) prefix lineNumbers None generateAnchors None None
let doc = Transformations.replaceLiterateParagraphs ctx doc
- let doc = MarkdownDocument(doc.Paragraphs @ [InlineBlock doc.FormattedTips], doc.DefinedLinks)
+ let doc = MarkdownDocument(doc.Paragraphs @ [InlineBlock(doc.FormattedTips, None)], doc.DefinedLinks)
Html.formatMarkdown writer ctx.GenerateHeaderAnchors Environment.NewLine true doc.DefinedLinks doc.Paragraphs
static member WriteLatex(doc:LiterateDocument, ?prefix, ?lineNumbers, ?generateAnchors) =
diff --git a/src/FSharp.Literate/ParseScript.fs b/src/FSharp.Literate/ParseScript.fs
index 70edf8308..af7fefc81 100644
--- a/src/FSharp.Literate/ParseScript.fs
+++ b/src/FSharp.Literate/ParseScript.fs
@@ -35,7 +35,7 @@ module internal CodeBlockUtils =
let rec readComments inWhite acc = function
| Token(TokenKind.Comment, text, _)::tokens when not inWhite->
readComments false (text::acc) tokens
- | Token(TokenKind.Default, String.WhiteSpace _, _)::tokens ->
+ | Token(TokenKind.Default, String.WhiteSpaceS _, _)::tokens ->
readComments true acc tokens
| [] -> Some(String.concat "" (List.rev acc))
| _ -> None
@@ -54,7 +54,7 @@ module internal CodeBlockUtils =
cend
match lines with
- | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines ->
+ | (ConcatenatedComments(String.StartsAndEndsWithS ("(***", "***)") (ParseCommandsS cmds)))::lines ->
// Ended with a command, yield comment, command & parse the next as a snippet
let cend = findCommentEnd comment
yield BlockComment (comment.Substring(0, cend))
@@ -68,7 +68,7 @@ module internal CodeBlockUtils =
yield BlockComment (comment.Substring(0, cend))
yield! collectSnippet [] lines
- | (Line[Token(TokenKind.Comment, String.StartsWith "(**" text, _)])::lines ->
+ | (Line[Token(TokenKind.Comment, String.StartsWithS "(**" text, _)])::lines ->
// Another block of Markdown comment starting...
// Yield the previous snippet block and continue parsing more comments
let cend = findCommentEnd comment
@@ -94,13 +94,13 @@ module internal CodeBlockUtils =
BlockSnippet res
seq {
match lines with
- | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines ->
+ | (ConcatenatedComments(String.StartsAndEndsWithS ("(***", "***)") (ParseCommandsS cmds)))::lines ->
// Found a special command, yield snippet, command and parse another snippet
if acc <> [] then yield blockSnippet acc
yield BlockCommand cmds
yield! collectSnippet [] lines
- | (Line[Token(TokenKind.Comment, String.StartsWith "(**" text, _)])::lines ->
+ | (Line[Token(TokenKind.Comment, String.StartsWithS "(**" text, _)])::lines ->
// Found a comment - yield snippet & switch to parsing comment state
// (Also trim leading spaces to support e.g.: `(** ## Hello **)`)
if acc <> [] then yield blockSnippet acc
@@ -132,19 +132,19 @@ module internal ParseScript =
// Reference to code snippet defined later
| BlockCommand(Command "include" ref)::blocks ->
- let p = EmbedParagraphs(CodeReference(ref))
+ let p = EmbedParagraphs(CodeReference(ref), None)
transformBlocks noEval (p::acc) defs blocks
| BlockCommand(Command "include-output" ref)::blocks ->
- let p = EmbedParagraphs(OutputReference(ref))
+ let p = EmbedParagraphs(OutputReference(ref), None)
transformBlocks noEval (p::acc) defs blocks
| BlockCommand(Command "include-it" ref)::blocks ->
- let p = EmbedParagraphs(ItValueReference(ref))
+ let p = EmbedParagraphs(ItValueReference(ref), None)
transformBlocks noEval (p::acc) defs blocks
| BlockCommand(Command "include-value" ref)::blocks ->
- let p = EmbedParagraphs(ValueReference(ref))
+ let p = EmbedParagraphs(ValueReference(ref), None)
transformBlocks noEval (p::acc) defs blocks
| BlockCommand(Command "raw" _) ::BlockSnippet(snip):: blocks ->
- let p = EmbedParagraphs(RawBlock(snip))
+ let p = EmbedParagraphs(RawBlock(snip), None)
transformBlocks noEval (p::acc) defs blocks
// Parse commands in [foo=bar,zoo], followed by a source code snippet
@@ -166,7 +166,7 @@ module internal ParseScript =
{ Evaluate = not (noEval || cmds.ContainsKey("do-not-eval"))
OutputName = outputName
Visibility = visibility }
- let code = EmbedParagraphs(LiterateCode(snip, opts))
+ let code = EmbedParagraphs(LiterateCode(snip, opts), None)
transformBlocks noEval (code::acc) defs blocks
// Unknown command
@@ -178,7 +178,7 @@ module internal ParseScript =
transformBlocks noEval acc defs blocks
// Ordinary F# code snippet
| BlockSnippet(snip)::blocks ->
- let p = EmbedParagraphs(FormattedCode(snip))
+ let p = EmbedParagraphs(FormattedCode(snip), None)
transformBlocks noEval (p::acc) defs blocks
// Markdown documentation block
| BlockComment(text)::blocks ->
diff --git a/src/FSharp.Literate/Transformations.fs b/src/FSharp.Literate/Transformations.fs
index 141a42ca0..daba38a90 100644
--- a/src/FSharp.Literate/Transformations.fs
+++ b/src/FSharp.Literate/Transformations.fs
@@ -21,10 +21,10 @@ module Transformations =
/// to colorize. We skip snippets that specify non-fsharp langauge e.g. [lang=csharp].
let rec collectCodeSnippets par = seq {
match par with
- | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), language, _)
+ | CodeBlock((String.StartsWithWrappedS ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), language, _, _)
when (not (String.IsNullOrWhiteSpace(language)) && language <> "fsharp") || (cmds.ContainsKey("lang") && cmds.["lang"] <> "fsharp") -> ()
- | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), _, _)
- | CodeBlock(Let (dict []) (cmds, code), _, _) ->
+ | CodeBlock((String.StartsWithWrappedS ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), _, _, _)
+ | CodeBlock(Let (dict []) (cmds, code), _, _, _) ->
let modul =
match cmds.TryGetValue("module") with
| true, v -> Some v | _ -> None
@@ -39,8 +39,8 @@ module Transformations =
/// Replace CodeBlock elements with formatted HTML that was processed by the F# snippets tool
/// (The dictionary argument is a map from original code snippets to formatted HTML snippets.)
let rec replaceCodeSnippets path (codeLookup:IDictionary<_, _>) = function
- | CodeBlock ((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), language, _)
- | CodeBlock(Let (dict []) (cmds, code), language, _) ->
+ | CodeBlock ((String.StartsWithWrappedS ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), language, _, r)
+ | CodeBlock(Let (dict []) (cmds, code), language, _, r) ->
if cmds.ContainsKey("hide") then None else
let code =
if cmds.ContainsKey("file") && cmds.ContainsKey("key") then
@@ -56,12 +56,12 @@ module Transformations =
else code
let lang =
match language with
- | String.WhiteSpace when cmds.ContainsKey("lang") -> cmds.["lang"]
+ | String.WhiteSpaceS when cmds.ContainsKey("lang") -> cmds.["lang"]
| language -> language
if not (String.IsNullOrWhiteSpace(lang)) && lang <> "fsharp" then
- Some (EmbedParagraphs(LanguageTaggedCode(lang, code)))
+ Some (EmbedParagraphs(LanguageTaggedCode(lang, code), r))
else
- Some (EmbedParagraphs(FormattedCode(codeLookup.[code])))
+ Some (EmbedParagraphs(FormattedCode(codeLookup.[code]), r))
// Recursively process nested paragraphs, other nodes return without change
| Matching.ParagraphNested(pn, nested) ->
@@ -116,7 +116,7 @@ module Transformations =
// Collect IndirectLinks in a span
let rec collectSpanReferences span = seq {
match span with
- | IndirectLink(_, _, key) -> yield key
+ | IndirectLink(_, _, key, _) -> yield key
| Matching.SpanLeaf _ -> ()
| Matching.SpanNode(_, spans) ->
for s in spans do yield! collectSpanReferences s }
@@ -137,13 +137,13 @@ module Transformations =
let replaceReferences (refIndex:IDictionary) =
// Replace IndirectLinks with a nice link given a single span element
let rec replaceSpans = function
- | IndirectLink(body, original, key) ->
- [ yield IndirectLink(body, original, key)
+ | IndirectLink(body, original, key, r) ->
+ [ yield IndirectLink(body, original, key, r)
match refIndex.TryGetValue(key) with
| true, i ->
- yield Literal " ["
- yield DirectLink([Literal (string i)], ("#rf" + DateTime.Now.ToString("yyMMddhh"), None))
- yield Literal "]"
+ yield Literal(" [", r)
+ yield DirectLink([Literal (string i, r)], ("#rf" + DateTime.Now.ToString("yyMMddhh"), None), r)
+ yield Literal("]", r)
| _ -> () ]
| Matching.SpanLeaf(sl) -> [Matching.SpanLeaf(sl)]
| Matching.SpanNode(nd, spans) ->
@@ -179,18 +179,18 @@ module Transformations =
if colon > 0 then
let auth = title.Substring(0, colon)
let name = title.Substring(colon + 1, title.Length - 1 - colon)
- yield [Span [ Literal (sprintf "[%d] " i)
- DirectLink([Literal (name.Trim())], (link, Some title))
- Literal (" - " + auth)] ]
+ yield [Span([ Literal (sprintf "[%d] " i, None)
+ DirectLink([Literal (name.Trim(), None)], (link, Some title), None)
+ Literal (" - " + auth, None)], None) ]
else
- yield [Span [ Literal (sprintf "[%d] " i)
- DirectLink([Literal title], (link, Some title))]] ]
+ yield [Span([ Literal (sprintf "[%d] " i, None)
+ DirectLink([Literal(title, None)], (link, Some title), None)], None)] ]
// Return the document together with dictionary for looking up indices
let id = DateTime.Now.ToString("yyMMddhh")
- [ Paragraph [AnchorLink id];
- Heading(3, [Literal "References"])
- ListBlock(MarkdownListKind.Unordered, refList) ], refLookup
+ [ Paragraph([AnchorLink(id, None)], None)
+ Heading(3, [Literal("References", None)], None)
+ ListBlock(MarkdownListKind.Unordered, refList, None) ], refLookup
/// Turn all indirect links into a references
/// and add paragraph to the document
@@ -270,8 +270,8 @@ module Transformations =
| _ -> None
match special with
| EvalFormat(Some result, _, kind) -> ctx.Evaluator.Value.Format(result, kind)
- | EvalFormat(None, ref, _) -> [ CodeBlock("Could not find reference '" + ref + "'", "", "") ]
- | other -> [ EmbedParagraphs(other) ]
+ | EvalFormat(None, ref, _) -> [ CodeBlock("Could not find reference '" + ref + "'", "", "", None) ]
+ | other -> [ EmbedParagraphs(other, None) ]
// Traverse all other structrues recursively
| Matching.ParagraphNested(pn, nested) ->
@@ -311,7 +311,7 @@ module Transformations =
let rec replaceSpecialCodes ctx (formatted:IDictionary<_, _>) = function
| Matching.LiterateParagraph(special) ->
match special with
- | RawBlock lines -> Some (InlineBlock (unparse lines))
+ | RawBlock lines -> Some (InlineBlock(unparse lines, None))
| LiterateCode(_, { Visibility = (HiddenCode | NamedCode _) }) -> None
| FormattedCode lines
| LiterateCode(lines, _) -> Some (formatted.[Choice1Of2 lines])
@@ -352,7 +352,7 @@ module Transformations =
| OutputKind.Latex ->
sprintf "\\begin{lstlisting}\n%s\n\\end{lstlisting}" code
- Some(InlineBlock(inlined))
+ Some(InlineBlock(inlined, None))
// Traverse all other structures recursively
| Matching.ParagraphNested(pn, nested) ->
let nested = List.map (List.choose (replaceSpecialCodes ctx formatted)) nested
@@ -379,7 +379,7 @@ module Transformations =
| OutputKind.Latex -> CodeFormat.FormatLatex(snippets, ctx.GenerateLineNumbers)
let lookup =
[ for (key, _), fmtd in Seq.zip replacements formatted.Snippets ->
- key, InlineBlock(fmtd.Content) ] |> dict
+ key, InlineBlock(fmtd.Content, None) ] |> dict
// Replace original snippets with formatted HTML/Latex and return document
let newParagraphs = List.choose (replaceSpecialCodes ctx lookup) doc.Paragraphs
diff --git a/src/FSharp.Markdown/HtmlFormatting.fs b/src/FSharp.Markdown/HtmlFormatting.fs
index a256cd19c..02bcb978b 100644
--- a/src/FSharp.Markdown/HtmlFormatting.fs
+++ b/src/FSharp.Markdown/HtmlFormatting.fs
@@ -67,19 +67,19 @@ let noBreak (ctx:FormattingContext) () = ()
/// Write MarkdownSpan value to a TextWriter
let rec formatSpan (ctx:FormattingContext) = function
- | LatexDisplayMath(body) ->
+ | LatexDisplayMath(body, _) ->
// use mathjax grammar, for detail, check: http://www.mathjax.org/
ctx.Writer.Write("\\[" + (htmlEncode body) + "\\]")
- | LatexInlineMath(body) ->
+ | LatexInlineMath(body, _) ->
// use mathjax grammar, for detail, check: http://www.mathjax.org/
ctx.Writer.Write("\\(" + (htmlEncode body) + "\\)")
- | AnchorLink(id) -> ctx.Writer.Write("")
- | EmbedSpans(cmd) -> formatSpans ctx (cmd.Render())
- | Literal(str) -> ctx.Writer.Write(str)
- | HardLineBreak -> ctx.Writer.Write(" " + ctx.Newline)
- | IndirectLink(body, _, LookupKey ctx.Links (link, title))
- | DirectLink(body, (link, title)) ->
+ | AnchorLink(id, _) -> ctx.Writer.Write("")
+ | EmbedSpans(cmd, _) -> formatSpans ctx (cmd.Render())
+ | Literal(str, _) -> ctx.Writer.Write(str)
+ | HardLineBreak(_) -> ctx.Writer.Write(" " + ctx.Newline)
+ | IndirectLink(body, _, LookupKey ctx.Links (link, title), _)
+ | DirectLink(body, (link, title), _) ->
ctx.Writer.Write("")
- | IndirectLink(body, original, _) ->
+ | IndirectLink(body, original, _, _) ->
ctx.Writer.Write("[")
formatSpans ctx body
ctx.Writer.Write("]")
ctx.Writer.Write(original)
- | IndirectImage(body, _, LookupKey ctx.Links (link, title))
- | DirectImage(body, (link, title)) ->
+ | IndirectImage(body, _, LookupKey ctx.Links (link, title), _)
+ | DirectImage(body, (link, title), _) ->
ctx.Writer.Write(" ()
ctx.Writer.Write("\" />")
- | IndirectImage(body, original, _) ->
+ | IndirectImage(body, original, _, _) ->
ctx.Writer.Write("[")
ctx.Writer.Write(body)
ctx.Writer.Write("]")
ctx.Writer.Write(original)
- | Strong(body) ->
+ | Strong(body, _) ->
ctx.Writer.Write("")
formatSpans ctx body
ctx.Writer.Write("")
- | InlineCode(body) ->
+ | InlineCode(body, _) ->
ctx.Writer.Write("")
ctx.Writer.Write(htmlEncode body)
ctx.Writer.Write("")
- | Emphasis(body) ->
+ | Emphasis(body, _) ->
ctx.Writer.Write("")
formatSpans ctx body
ctx.Writer.Write("")
@@ -141,10 +141,10 @@ let formatAnchor (ctx:FormattingContext) (spans:MarkdownSpans) =
let rec gather (span:MarkdownSpan) : seq =
seq {
match span with
- | Literal str -> yield! extractWords str
- | Strong body -> yield! gathers body
- | Emphasis body -> yield! gathers body
- | DirectLink (body,_) -> yield! gathers body
+ | Literal(str, _) -> yield! extractWords str
+ | Strong(body, _) -> yield! gathers body
+ | Emphasis(body, _) -> yield! gathers body
+ | DirectLink(body, _, _) -> yield! gathers body
| _ -> ()
}
@@ -163,13 +163,13 @@ let withInner ctx f =
/// Write a MarkdownParagraph value to a TextWriter
let rec formatParagraph (ctx:FormattingContext) paragraph =
match paragraph with
- | LatexBlock(lines) ->
+ | LatexBlock(lines, _) ->
// use mathjax grammar, for detail, check: http://www.mathjax.org/
let body = String.concat ctx.Newline lines
ctx.Writer.Write("
")
ctx.Writer.Write(htmlEncode code)
diff --git a/src/FSharp.Markdown/MarkdownParser.fs b/src/FSharp.Markdown/MarkdownParser.fs
index 373b531ac..ecac2f16d 100644
--- a/src/FSharp.Markdown/MarkdownParser.fs
+++ b/src/FSharp.Markdown/MarkdownParser.fs
@@ -18,7 +18,7 @@ open FSharp.Collections
/// Splits a link formatted as `http://link "title"` into a link part
/// and an optional title part (may be wrapped using quote or double-quotes)
-let getLinkAndTitle (String.TrimBoth(input, n)) =
+let getLinkAndTitle (StringPosition.TrimBoth(input, n)) =
let url, title =
if input.Length = 0 then "", None else
let c = input.[input.Length - 1]
@@ -250,7 +250,7 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
yield! accLiterals.Value }
/// Parse body of a paragraph into a list of Markdown inline spans
-let parseSpans (String.TrimBoth(s, n)) ctx =
+let parseSpans (StringPosition.TrimBoth(s, n)) ctx =
parseChars [] (s.ToCharArray() |> List.ofArray) ctx |> List.ofSeq
let rec trimSpaces numSpaces (s:string) =
@@ -265,11 +265,11 @@ let rec trimSpaces numSpaces (s:string) =
/// Recognizes heading, either prefixed with #s or followed by === or --- line
let (|Heading|_|) = function
- | (String.TrimBoth header) :: (String.TrimEnd (String.EqualsRepeated("=", 0))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("=", 0))) :: rest ->
Some(1, header, rest)
- | (String.TrimBoth header) :: (String.TrimEnd (String.EqualsRepeated("-", 0))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("-", 0))) :: rest ->
Some(2, header, rest)
- | String.StartsWithRepeated "#" (n, (header, ln)) :: rest ->
+ | StringPosition.StartsWithRepeated "#" (n, (header, ln)) :: rest ->
let header =
// Drop "##" at the end, but only when it is preceded by some whitespace
// (For example "## Hello F#" should be "Hello F#")
@@ -306,9 +306,9 @@ let (|NestedCodeBlock|_|) = function
/// Recognizes a fenced code block - starting and ending with at least ``` or ~~~
let (|FencedCodeBlock|_|) = function
- | String.StartsWithNTimesTrimIgnoreStartWhitespace "~" (Let "~" (start,num), indent, header) :: lines
+ | StringPosition.StartsWithNTimesTrimIgnoreStartWhitespace "~" (Let "~" (start,num), indent, header) :: lines
// when num > 2
- | String.StartsWithNTimesTrimIgnoreStartWhitespace "`" (Let "`" (start,num), indent, header) :: lines
+ | StringPosition.StartsWithNTimesTrimIgnoreStartWhitespace "`" (Let "`" (start,num), indent, header) :: lines
when num > 2 ->
let mutable endStr = String.replicate num start
if header.Contains (start) then None // info string cannot contain backspaces
@@ -317,7 +317,7 @@ let (|FencedCodeBlock|_|) = function
match [line] with
// end cannot contain info string afterwards (see http://spec.commonmark.org/0.23/#example-104)
// end must be indended with less then 4 spaces: http://spec.commonmark.org/0.23/#example-95
- | String.StartsWithNTimesTrimIgnoreStartWhitespace start (n, i, h) :: _ when n >= num && i < 4 && String.IsNullOrWhiteSpace h ->
+ | StringPosition.StartsWithNTimesTrimIgnoreStartWhitespace start (n, i, h) :: _ when n >= num && i < 4 && String.IsNullOrWhiteSpace h ->
endStr <- String.replicate n start
true
| _ -> false)
@@ -365,23 +365,23 @@ let (|SkipSomeNumbers|_|) (input:string, n:int) =
/// Recognizes a staring of a list (either 1. or +, *, -).
/// Returns the rest of the line, together with the indent.
let (|ListStart|_|) = function
- | String.TrimStartAndCount
+ | StringPosition.TrimStartAndCount
(startIndent, spaces,
// NOTE: a tab character after +, * or - isn't supported by the reference implementation
// (it will be parsed as paragraph for 0.22)
- (String.StartsWithAny ["+ "; "* "; "- " (*; "+\t"; "*\t"; "-\t"*)] as item)) ->
+ (StringPosition.StartsWithAny ["+ "; "* "; "- " (*; "+\t"; "*\t"; "-\t"*)] as item)) ->
let li = ((fst item).Substring(2), snd item)
- let (String.TrimStartAndCount (startIndent2, spaces2, _)) = li
+ let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = li
let endIndent =
startIndent + 2 +
// Handle case of code block
if startIndent2 >= 5 then 1 else startIndent2
Some(Unordered, startIndent, endIndent, li)
- | String.TrimStartAndCount // Remove leading spaces
+ | StringPosition.TrimStartAndCount // Remove leading spaces
(startIndent, spaces,
(SkipSomeNumbers // read a number
(skipNumCount, '.' :: ' ' :: List.AsString item))) ->
- let (String.TrimStartAndCount (startIndent2, spaces2, _)) = (item, 0)
+ let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = (item, 0)
let endIndent =
startIndent + 2 + skipNumCount +
// Handle case of code block
@@ -392,13 +392,13 @@ let (|ListStart|_|) = function
/// Splits input into lines until whitespace or starting of a list and the rest.
let (|LinesUntilListOrWhite|) =
List.partitionUntil (function
- | ListStart _ | String.WhiteSpace -> true | _ -> false)
+ | ListStart _ | StringPosition.WhiteSpace -> true | _ -> false)
/// Splits input into lines until not-indented line or starting of a list and the rest.
let (|LinesUntilListOrUnindented|) =
List.partitionUntilLookahead (function
- | (ListStart _ | String.Unindented)::_
- | String.WhiteSpace::String.WhiteSpace::_ -> true | _ -> false)
+ | (ListStart _ | StringPosition.Unindented)::_
+ | StringPosition.WhiteSpace::StringPosition.WhiteSpace::_ -> true | _ -> false)
/// Recognizes a list item until the next list item (possibly nested) or end of a list.
/// The parameter specifies whether the previous line was simple (single-line not
@@ -414,17 +414,17 @@ let (|ListItem|_|) prevSimple = function
(LinesUntilListOrUnindented (more, rest) as next)) ->
let simple =
match item with
- | String.TrimStartAndCount (_, spaces, _) when spaces >= 4->
+ | StringPosition.TrimStartAndCount (_, spaces, _) when spaces >= 4->
// Code Block
false
| _ ->
match next, rest with
- | String.WhiteSpace::_, (ListStart _)::_ -> false
+ | StringPosition.WhiteSpace::_, (ListStart _)::_ -> false
| (ListStart _)::_, _ -> true
| [], _ -> true
- | [ String.WhiteSpace ], _ -> true
- | String.WhiteSpace::String.WhiteSpace::_, _ -> true
- | _, String.Unindented::_ -> prevSimple
+ | [ StringPosition.WhiteSpace ], _ -> true
+ | StringPosition.WhiteSpace::StringPosition.WhiteSpace::_, _ -> true
+ | _, StringPosition.Unindented::_ -> prevSimple
| _, _ -> false
let lines =
@@ -486,10 +486,10 @@ let rec pipeTableFindSplits (delim : char array) (line : char list) =
/// Recognizes alignment specified in the passed separator line.
let (|TableCellSeparator|_|) = function
- | String.StartsAndEndsWith (":", ":") (String.EqualsRepeated("-", 0)) -> Some(AlignCenter)
- | String.StartsWith ":" (String.EqualsRepeated("-", 0)) -> Some(AlignLeft)
- | String.StartsAndEndsWith ("", ":") (String.EqualsRepeated("-", 0)) -> Some(AlignRight)
- | String.EqualsRepeated("-", 0) -> Some(AlignDefault)
+ | StringPosition.StartsAndEndsWith (":", ":") (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignCenter)
+ | StringPosition.StartsWith ":" (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignLeft)
+ | StringPosition.StartsAndEndsWith ("", ":") (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignRight)
+ | StringPosition.EqualsRepeated("-", 0) -> Some(AlignDefault)
| _ -> None
/// Recognizes row of pipe table.
@@ -562,9 +562,9 @@ let (|EmacsTableLine|_|) (grid:option) (c:char) (check:string * int -> b
/// Recognizes emacs table
let (|EmacsTableBlock|_|) (input) =
- let isCellSep = String.(|EqualsRepeated|_|)("-", 0) >> Option.isSome
+ let isCellSep = StringPosition.(|EqualsRepeated|_|)("-", 0) >> Option.isSome
let isAlignedCellSep = ( |TableCellSeparator|_| ) >> Option.isSome
- let isHeadCellSep = String.(|EqualsRepeated|_|)("=", 0) >> Option.isSome
+ let isHeadCellSep = StringPosition.(|EqualsRepeated|_|)("=", 0) >> Option.isSome
let isText (s:string, n:int) = true
match input with
| (EmacsTableLine None '+' isAlignedCellSep (grid, parts)) :: rest ->
@@ -605,7 +605,7 @@ let (|TakeParagraphLines|_|) input =
| Heading _ -> false
| FencedCodeBlock _ -> false
| BlockquoteStart _::_ -> false
- | String.WhiteSpace::_ -> false
+ | StringPosition.WhiteSpace::_ -> false
| _ -> true) input with
| matching, rest when matching <> [] -> Some(matching, rest)
| _ -> None
@@ -623,7 +623,7 @@ let (|LinesUntilBlockquoteEnds|) input =
match next with
| BlockquoteStart _ :: _
| Heading _
- | String.WhiteSpace :: _ -> true
+ | StringPosition.WhiteSpace :: _ -> true
| _ ->
false) input
@@ -643,8 +643,8 @@ let rec (|Blockquote|_|) = function
/// Recognizes a special case: an empty blockquote line should terminate
/// the blockquote if the next line is not a blockquote
and (|EmptyBlockquote|_|) = function
- | BlockquoteStart(String.WhiteSpace) :: Blockquote(_) -> None
- | BlockquoteStart(String.WhiteSpace) :: rest -> Some rest
+ | BlockquoteStart(StringPosition.WhiteSpace) :: Blockquote(_) -> None
+ | BlockquoteStart(StringPosition.WhiteSpace) :: rest -> Some rest
| _ -> None
/// Recognizes Latex block - start with "$$$"
@@ -656,10 +656,10 @@ let (|LatexBlock|_|) (lines:(string * int) list) = lines |> function
/// Recognize a definition of a link as in `[key]: http://url ...`
let (|LinkDefinition|_|) = function
- | ( String.StartsWithWrapped ("[", "]:") (wrapped, String.TrimBoth link)
- | String.StartsWithWrapped (" [", "]:") (wrapped, String.TrimBoth link)
- | String.StartsWithWrapped (" [", "]:") (wrapped, String.TrimBoth link)
- | String.StartsWithWrapped (" [", "]:") (wrapped, String.TrimBoth link) ) :: rest ->
+ | ( StringPosition.StartsWithWrapped ("[", "]:") (wrapped, StringPosition.TrimBoth link)
+ | StringPosition.StartsWithWrapped (" [", "]:") (wrapped, StringPosition.TrimBoth link)
+ | StringPosition.StartsWithWrapped (" [", "]:") (wrapped, StringPosition.TrimBoth link)
+ | StringPosition.StartsWithWrapped (" [", "]:") (wrapped, StringPosition.TrimBoth link) ) :: rest ->
Some((wrapped, link), rest)
| _ -> None
diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs
index 238d35972..16ad5d774 100755
--- a/src/FSharp.MetadataFormat/Main.fs
+++ b/src/FSharp.MetadataFormat/Main.fs
@@ -535,7 +535,7 @@ module Reader =
Comment.Create(blurb, full, sections)
let findCommand = (function
- | String.StartsWithWrapped ("[", "]") (ParseCommand(k, v), rest) ->
+ | StringPosition.StartsWithWrapped ("[", "]") (ParseCommand(k, v), rest) ->
Some (k, v)
| _ -> None)
From c7f78e7a9d80acf2d2b237b01928d763feda5554 Mon Sep 17 00:00:00 2001
From: Avi Avni
Date: Wed, 6 Jul 2016 20:28:06 +0300
Subject: [PATCH 3/6] fix naming
---
src/Common/StringParsing.fs | 16 +---------------
src/FSharp.Literate/ParseScript.fs | 4 ++--
src/FSharp.Literate/Transformations.fs | 6 +++---
3 files changed, 6 insertions(+), 20 deletions(-)
diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs
index cd8b0ba72..72cd7f34e 100644
--- a/src/Common/StringParsing.fs
+++ b/src/Common/StringParsing.fs
@@ -303,21 +303,7 @@ open System.Collections.Generic
/// recognize `key1=value, key2=value` and also `key1:value, key2:value`
/// The key of the command should be identifier with just
/// characters in it - otherwise, the parsing fails.
-let (|ParseCommandsS|_|) (str:string) =
- let kvs =
- [ for cmd in str.Split(',') do
- let kv = cmd.Split([| '='; ':' |])
- if kv.Length = 2 then yield kv.[0].Trim(), kv.[1].Trim()
- elif kv.Length = 1 then yield kv.[0].Trim(), "" ]
- let allKeysValid =
- kvs |> Seq.forall (fst >> Seq.forall (fun c -> Char.IsLetter c || c = '_' || c = '-'))
- if allKeysValid && kvs <> [] then Some(dict kvs) else None
-
-/// Utility for parsing commands. Commands can be used in different places. We
-/// recognize `key1=value, key2=value` and also `key1:value, key2:value`
-/// The key of the command should be identifier with just
-/// characters in it - otherwise, the parsing fails.
-let (|ParseCommands|_|) (str:string, n:int) =
+let (|ParseCommands|_|) (str:string) =
let kvs =
[ for cmd in str.Split(',') do
let kv = cmd.Split([| '='; ':' |])
diff --git a/src/FSharp.Literate/ParseScript.fs b/src/FSharp.Literate/ParseScript.fs
index 9e767d962..34e2c4076 100644
--- a/src/FSharp.Literate/ParseScript.fs
+++ b/src/FSharp.Literate/ParseScript.fs
@@ -54,7 +54,7 @@ module internal CodeBlockUtils =
cend
match lines with
- | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommandsS cmds)))::lines ->
+ | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines ->
// Ended with a command, yield comment, command & parse the next as a snippet
let cend = findCommentEnd comment
yield BlockComment (comment.Substring(0, cend))
@@ -94,7 +94,7 @@ module internal CodeBlockUtils =
BlockSnippet res
seq {
match lines with
- | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommandsS cmds)))::lines ->
+ | (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines ->
// Found a special command, yield snippet, command and parse another snippet
if acc <> [] then yield blockSnippet acc
yield BlockCommand cmds
diff --git a/src/FSharp.Literate/Transformations.fs b/src/FSharp.Literate/Transformations.fs
index 77f91751d..d5253e096 100644
--- a/src/FSharp.Literate/Transformations.fs
+++ b/src/FSharp.Literate/Transformations.fs
@@ -21,9 +21,9 @@ module Transformations =
/// to colorize. We skip snippets that specify non-fsharp langauge e.g. [lang=csharp].
let rec collectCodeSnippets par = seq {
match par with
- | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), language, _, _)
+ | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), language, _, _)
when (not (String.IsNullOrWhiteSpace(language)) && language <> "fsharp") || (cmds.ContainsKey("lang") && cmds.["lang"] <> "fsharp") -> ()
- | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), _, _, _)
+ | CodeBlock((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), _, _, _)
| CodeBlock(Let (dict []) (cmds, code), _, _, _) ->
let modul =
match cmds.TryGetValue("module") with
@@ -39,7 +39,7 @@ module Transformations =
/// Replace CodeBlock elements with formatted HTML that was processed by the F# snippets tool
/// (The dictionary argument is a map from original code snippets to formatted HTML snippets.)
let rec replaceCodeSnippets path (codeLookup:IDictionary<_, _>) = function
- | CodeBlock ((String.StartsWithWrapped ("[", "]") (ParseCommandsS cmds, String.SkipSingleLine code)), language, _, r)
+ | CodeBlock ((String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.SkipSingleLine code)), language, _, r)
| CodeBlock(Let (dict []) (cmds, code), language, _, r) ->
if cmds.ContainsKey("hide") then None else
let code =
From 377f5b1f3cf0e9dcf1e9fdc518a703e7b98c613b Mon Sep 17 00:00:00 2001
From: Avi Avni
Date: Wed, 6 Jul 2016 21:17:50 +0300
Subject: [PATCH 4/6] add names to DUs parameters
---
src/FSharp.Markdown/Markdown.fs | 48 ++++++++++++++++-----------------
1 file changed, 24 insertions(+), 24 deletions(-)
diff --git a/src/FSharp.Markdown/Markdown.fs b/src/FSharp.Markdown/Markdown.fs
index afd942d4d..e2d760295 100644
--- a/src/FSharp.Markdown/Markdown.fs
+++ b/src/FSharp.Markdown/Markdown.fs
@@ -30,19 +30,19 @@ type MarkdownColumnAlignment =
/// Represents inline formatting inside a paragraph. This can be literal (with text), various
/// formattings (string, emphasis, etc.), hyperlinks, images, inline maths etc.
type MarkdownSpan =
- | Literal of string * MarkdownRange option
- | InlineCode of string * MarkdownRange option
- | Strong of MarkdownSpans * MarkdownRange option
- | Emphasis of MarkdownSpans * MarkdownRange option
- | AnchorLink of string * MarkdownRange option
- | DirectLink of MarkdownSpans * (string * option) * MarkdownRange option
- | IndirectLink of MarkdownSpans * string * string * MarkdownRange option
- | DirectImage of string * (string * option) * MarkdownRange option
- | IndirectImage of string * string * string * MarkdownRange option
- | HardLineBreak of MarkdownRange option
- | LatexInlineMath of string * MarkdownRange option
- | LatexDisplayMath of string * MarkdownRange option
- | EmbedSpans of MarkdownEmbedSpans * MarkdownRange option
+ | Literal of text:string * range:MarkdownRange option
+ | InlineCode of code:string * range:MarkdownRange option
+ | Strong of spans:MarkdownSpans * range:MarkdownRange option
+ | Emphasis of spans:MarkdownSpans * range:MarkdownRange option
+ | AnchorLink of link:string * range:MarkdownRange option
+ | DirectLink of spans:MarkdownSpans * linkAndTitle:(string * option) * range:MarkdownRange option
+ | IndirectLink of spans:MarkdownSpans * link:string * key:string * range:MarkdownRange option
+ | DirectImage of body:string * linkAndTitle:(string * option) * range:MarkdownRange option
+ | IndirectImage of body:string * link:string * key:string * range:MarkdownRange option
+ | HardLineBreak of range:MarkdownRange option
+ | LatexInlineMath of string * range:MarkdownRange option
+ | LatexDisplayMath of string * range:MarkdownRange option
+ | EmbedSpans of customSpans:MarkdownEmbedSpans * range:MarkdownRange option
/// A type alias for a list of `MarkdownSpan` values
and MarkdownSpans = list
@@ -56,17 +56,17 @@ and MarkdownEmbedSpans =
/// Paragraphs are headings, inline paragraphs, code blocks, lists, quotations, tables and
/// also embedded LaTeX blocks.
type MarkdownParagraph =
- | Heading of int * MarkdownSpans * MarkdownRange option
- | Paragraph of MarkdownSpans * MarkdownRange option
- | CodeBlock of string * string * string * MarkdownRange option
- | InlineBlock of string * MarkdownRange option
- | ListBlock of MarkdownListKind * list * MarkdownRange option
- | QuotedBlock of MarkdownParagraphs * MarkdownRange option
- | Span of MarkdownSpans * MarkdownRange option
- | LatexBlock of list * MarkdownRange option
- | HorizontalRule of char * MarkdownRange option
- | TableBlock of option * list * list * MarkdownRange option
- | EmbedParagraphs of MarkdownEmbedParagraphs * MarkdownRange option
+ | Heading of n:int * spans:MarkdownSpans * range:MarkdownRange option
+ | Paragraph of spans:MarkdownSpans * range:MarkdownRange option
+ | CodeBlock of code:string * language:string * ignoredLine:string * range:MarkdownRange option
+ | InlineBlock of code:string * range:MarkdownRange option
+ | ListBlock of kind:MarkdownListKind * items:list * range:MarkdownRange option
+ | QuotedBlock of paragraphs:MarkdownParagraphs * range:MarkdownRange option
+ | Span of spans:MarkdownSpans * range:MarkdownRange option
+ | LatexBlock of body:list * range:MarkdownRange option
+ | HorizontalRule of c:char * range:MarkdownRange option
+ | TableBlock of headers:option * alignments:list * rows:list * range:MarkdownRange option
+ | EmbedParagraphs of customParagraphs:MarkdownEmbedParagraphs * range:MarkdownRange option
/// A type alias for a list of paragraphs
and MarkdownParagraphs = list
From 16383d8894bef27d0d273b7775bda703a6238072 Mon Sep 17 00:00:00 2001
From: Avi Avni
Date: Fri, 8 Jul 2016 18:25:15 +0300
Subject: [PATCH 5/6] add start/end line/column to markdown range
---
docs/content/codeformat.fsx | 2 +-
docs/content/markdown.fsx | 9 +-
src/Common/StringParsing.fs | 67 +++++----
src/FSharp.CodeFormat/CodeFormatAgent.fs | 6 +-
src/FSharp.CodeFormat/CommentFilter.fs | 5 +-
.../FSharp.Formatting.Common.fsproj | 1 +
src/FSharp.Formatting.Common/Range.fs | 11 ++
src/FSharp.Markdown/Main.fs | 7 +-
src/FSharp.Markdown/Markdown.fs | 7 +-
src/FSharp.Markdown/MarkdownParser.fs | 135 ++++++++++--------
src/FSharp.MetadataFormat/Main.fs | 4 +-
tests/FSharp.Literate.Tests/Tests.fs | 20 +--
.../FSharp.Markdown.Tests.fsproj | 5 +
tests/FSharp.Markdown.Tests/Markdown.fs | 37 ++---
14 files changed, 184 insertions(+), 132 deletions(-)
create mode 100644 src/FSharp.Formatting.Common/Range.fs
diff --git a/docs/content/codeformat.fsx b/docs/content/codeformat.fsx
index 570c84e12..12ae4d9bb 100644
--- a/docs/content/codeformat.fsx
+++ b/docs/content/codeformat.fsx
@@ -24,7 +24,7 @@ can be called to format snippets repeatedly:
*)
let fsharpCompiler = Assembly.Load("FSharp.Compiler")
-let formattingAgent = CodeFormat.CreateAgent(fsharpCompiler)
+let formattingAgent = CodeFormat.CreateAgent()
(**
If you want to process multiple snippets, it is a good idea to keep the
diff --git a/docs/content/markdown.fsx b/docs/content/markdown.fsx
index 87b1593d7..133127bda 100644
--- a/docs/content/markdown.fsx
+++ b/docs/content/markdown.fsx
@@ -10,7 +10,10 @@ First, we need to load the assembly and open necessary namespaces:
*)
#r "../../bin/FSharp.Markdown.dll"
+#r "../../bin/FSharp.Formatting.Common.dll"
open FSharp.Markdown
+open FSharp.Formatting.Common
+
(**
Parsing documents
@@ -64,7 +67,7 @@ The following snippet prints the heading of the document:
// Iterate over all the paragraph elements
for par in parsed.Paragraphs do
match par with
- | Heading(1, [Literal text]) ->
+ | Heading(1, [Literal(text, _)], _) ->
// Recognize heading that has a simple content
// containing just a literal (no other formatting)
printfn "%s" text
@@ -92,8 +95,8 @@ to recognize any paragraph or span that can contain child elements:
/// Returns all links in a specified span node
let rec collectSpanLinks span = seq {
match span with
- | DirectLink(_, (url, _)) -> yield url
- | IndirectLink(_, _, key) -> yield fst (parsed.DefinedLinks.[key])
+ | DirectLink(_, (url, _), _) -> yield url
+ | IndirectLink(_, _, key, _) -> yield fst (parsed.DefinedLinks.[key])
| Matching.SpanLeaf _ -> ()
| Matching.SpanNode(_, spans) ->
for s in spans do yield! collectSpanLinks s }
diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs
index 72cd7f34e..e33c38c92 100644
--- a/src/Common/StringParsing.fs
+++ b/src/Common/StringParsing.fs
@@ -7,6 +7,7 @@ module internal FSharp.Patterns
open System
open FSharp.Collections
+open FSharp.Formatting.Common
// --------------------------------------------------------------------------------------
// Active patterns that simplify parsing of strings and lists of strings (lines)
@@ -92,46 +93,55 @@ module String =
module StringPosition =
/// Matches when a string is a whitespace or null
- let (|WhiteSpace|_|) (s, n: int) =
+ let (|WhiteSpace|_|) (s, n: MarkdownRange) =
if String.IsNullOrWhiteSpace(s) then Some() else None
/// Matches when a string does starts with non-whitespace
- let (|Unindented|_|) (s:string, n:int) =
+ let (|Unindented|_|) (s:string, n:MarkdownRange) =
if not (String.IsNullOrWhiteSpace(s)) && s.TrimStart() = s then Some() else None
/// Returns a string trimmed from both start and end
- let (|TrimBoth|) (text:string, n:int) = (text.Trim(), n)
+ let (|TrimBoth|) (text:string, n:MarkdownRange) =
+ let trimmedStart = text.TrimStart()
+ let trimmed = trimmedStart.TrimEnd()
+ (trimmed, { n with StartColumn = n.StartColumn + text.Length - trimmedStart.Length; EndColumn = n.EndColumn - trimmedStart.Length + trimmed.Length })
/// Returns a string trimmed from the end
- let (|TrimEnd|) (text:string, n:int) = (text.TrimEnd(), n)
+ let (|TrimEnd|) (text:string, n:MarkdownRange) =
+ let trimmed = text.TrimEnd()
+ (trimmed, { n with EndColumn = n.EndColumn - text.Length + trimmed.Length })
/// Returns a string trimmed from the start
- let (|TrimStart|) (text:string, n:int) = (text.TrimStart(), n)
+ let (|TrimStart|) (text:string, n:MarkdownRange) =
+ let trimmed = text.TrimStart()
+ (trimmed, { n with StartColumn = n.StartColumn + text.Length - trimmed.Length })
- /// Retrusn a string trimmed from the end using characters given as a parameter
- let (|TrimEndUsing|) chars (text:string, n:int) = text.TrimEnd(Array.ofSeq chars)
+ /// Returns a string trimmed from the end using characters given as a parameter
+ let (|TrimEndUsing|) chars (text:string, n:MarkdownRange) =
+ let trimmed = text.TrimEnd(Array.ofSeq chars)
+ (trimmed, { n with EndColumn = n.EndColumn - text.Length + trimmed.Length })
/// Returns a string trimmed from the start together with
/// the number of skipped whitespace characters
- let (|TrimStartAndCount|) (text:string, n:int) =
+ let (|TrimStartAndCount|) (text:string, n:MarkdownRange) =
let trimmed = text.TrimStart([|' '; '\t'|])
let len = text.Length - trimmed.Length
- len, text.Substring(0, len).Replace("\t", " ").Length, (trimmed, n)
+ len, text.Substring(0, len).Replace("\t", " ").Length, (trimmed, { n with StartColumn = n.StartColumn + text.Length - trimmed.Length })
/// Matches when a string starts with any of the specified sub-strings
- let (|StartsWithAny|_|) (starts:seq) (text:string, n:int) =
+ let (|StartsWithAny|_|) (starts:seq) (text:string, n:MarkdownRange) =
if starts |> Seq.exists (text.StartsWith) then Some() else None
/// Matches when a string starts with the specified sub-string
- let (|StartsWith|_|) (start:string) (text:string, n:int) =
- if text.StartsWith(start) then Some(text.Substring(start.Length), n) else None
+ let (|StartsWith|_|) (start:string) (text:string, n:MarkdownRange) =
+ if text.StartsWith(start) then Some(text.Substring(start.Length), { n with StartColumn = n.StartColumn + text.Length - start.Length }) else None
/// Matches when a string starts with the specified sub-string
/// The matched string is trimmed from all whitespace.
- let (|StartsWithTrim|_|) (start:string) (text:string, n:int) =
- if text.StartsWith(start) then Some(text.Substring(start.Length).Trim(), n) else None
+ let (|StartsWithTrim|_|) (start:string) (text:string, n:MarkdownRange) =
+ if text.StartsWith(start) then Some(text.Substring(start.Length).Trim(), { n with StartColumn = n.StartColumn + text.Length - start.Length }) else None
/// Matches when a string starts with the specified sub-string (ignoring whitespace at the start)
/// The matched string is trimmed from all whitespace.
- let (|StartsWithNTimesTrimIgnoreStartWhitespace|_|) (start:string) (text:string, n:int) =
+ let (|StartsWithNTimesTrimIgnoreStartWhitespace|_|) (start:string) (text:string, n:MarkdownRange) =
if text.Contains(start) then
let beforeStart = text.Substring(0, text.IndexOf(start))
if String.IsNullOrWhiteSpace (beforeStart) then
@@ -147,10 +157,10 @@ module StringPosition =
/// Matches when a string starts with the given value and ends
/// with a given value (and returns the rest of it)
- let (|StartsAndEndsWith|_|) (starts, ends) (s:string, n:int) =
+ let (|StartsAndEndsWith|_|) (starts, ends) (s:string, n:MarkdownRange) =
if s.StartsWith(starts) && s.EndsWith(ends) &&
s.Length >= starts.Length + ends.Length then
- Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length), n)
+ Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length), { n with StartColumn = n.StartColumn + s.Length - starts.Length; EndColumn = n.EndColumn - s.Length + ends.Length })
else None
/// Matches when a string starts with the given value and ends
@@ -165,7 +175,7 @@ module StringPosition =
///
/// let (StartsWithRepeated "/\" (2, " abc")) = "/\/\ abc"
///
- let (|StartsWithRepeated|_|) (repeated:string) (text:string, ln:int) =
+ let (|StartsWithRepeated|_|) (repeated:string) (text:string, ln:MarkdownRange) =
let rec loop i =
if i = text.Length then i
elif text.[i] <> repeated.[i % repeated.Length] then i
@@ -173,25 +183,25 @@ module StringPosition =
let n = loop 0
if n = 0 || n % repeated.Length <> 0 then None
- else Some(n/repeated.Length, (text.Substring(n, text.Length - n), ln))
+ else Some(n/repeated.Length, (text.Substring(n, text.Length - n), { ln with StartColumn = n }))
/// Matches when a string starts with a sub-string wrapped using the
/// opening and closing sub-string specified in the parameter.
/// For example "[aa]bc" is wrapped in [ and ] pair. Returns the wrapped
/// text together with the rest.
- let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string, n:int) =
+ let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string, n:MarkdownRange) =
if text.StartsWith(starts) then
let id = text.IndexOf(ends, starts.Length)
if id >= 0 then
let wrapped = text.Substring(starts.Length, id - starts.Length)
let rest = text.Substring(id + ends.Length, text.Length - id - ends.Length)
- Some(wrapped, (rest, n))
+ Some(wrapped, (rest, { n with StartColumn = id + ends.Length }))
else None
else None
/// Matches when a string consists of some number of
/// complete repetitions of a specified sub-string.
- let (|EqualsRepeated|_|) (repeated, n:int) = function
+ let (|EqualsRepeated|_|) (repeated, n:MarkdownRange) = function
| StartsWithRepeated repeated (n, ("", _)) -> Some()
| _ -> None
@@ -201,7 +211,7 @@ module List =
let inline (|DelimitedWith|_|) startl endl input =
if List.startsWith startl input then
match List.partitionUntilEquals endl (List.skip startl.Length input) with
- | Some(pre, post) -> Some(pre, List.skip endl.Length post)
+ | Some(pre, post) -> Some(pre, List.skip endl.Length post, startl.Length, endl.Length)
| None -> None
else None
@@ -258,7 +268,7 @@ module Lines =
/// either empty (or whitespace) or start with at least 4 spaces (a tab counts as 4 spaces here).
/// Returns all such lines from the beginning until a different line and
/// the number of spaces the first line started with.
- let (|TakeCodeBlock|_|) (input:(string * int) list) =
+ let (|TakeCodeBlock|_|) (input:(string * MarkdownRange) list) =
let spaceNum = 4
//match input with
//| h :: _ ->
@@ -277,17 +287,18 @@ module Lines =
| _ -> None
/// Removes whitespace lines from the beginning of the list
- let (|TrimBlankStart|) = List.skipWhile (fun (s:string, n:int) -> String.IsNullOrWhiteSpace s)
+ let (|TrimBlankStart|) = List.skipWhile (fun (s:string, n:MarkdownRange) -> String.IsNullOrWhiteSpace s)
/// Trims all lines of the current paragraph
let (|TrimParagraphLines|) lines =
lines
// first remove all whitespace on the beginning of the line
- |> List.map (fun (s:string, n:int) -> s.TrimStart())
+ |> List.map StringPosition.(|TrimStart|)
// Now remove all additional spaces at the end, but keep two spaces if existent
- |> List.map (fun s ->
+ |> List.map (fun (s, n) ->
let endsWithTwoSpaces = s.EndsWith(" ")
- s.TrimEnd([|' '|]) + if endsWithTwoSpaces then " " else "")
+ let trimmed = s.TrimEnd([|' '|]) + if endsWithTwoSpaces then " " else ""
+ (trimmed, { n with EndColumn = n.EndColumn - s.Length + trimmed.Length }))
/// Parameterized pattern that assigns the specified value to the
/// first component of a tuple. Usage:
diff --git a/src/FSharp.CodeFormat/CodeFormatAgent.fs b/src/FSharp.CodeFormat/CodeFormatAgent.fs
index 1c7dfb1e0..29c3e291e 100644
--- a/src/FSharp.CodeFormat/CodeFormatAgent.fs
+++ b/src/FSharp.CodeFormat/CodeFormatAgent.fs
@@ -73,7 +73,7 @@ module private Helpers =
state := nstate
yield! parseLine()
| None, nstate -> state := nstate }
- yield n, parseLine() |> List.ofSeq ]
+ yield { StartLine = n; StartColumn = 0; EndLine = n; EndColumn = 0 }, parseLine() |> List.ofSeq ]
// Count the minimal number of spaces at the beginning of lines
// (so that we can remove spaces for indented text)
@@ -238,10 +238,10 @@ type CodeFormatAgent() =
processSnippetLine
checkResults
(categorizedSpans
- |> Map.tryFind ((fst snippetLine) + 1)
+ |> Map.tryFind ((fst snippetLine).StartLine + 1)
|> function None -> [] | Some spans -> List.ofSeq spans)
lines
- snippetLine)
+ ((fst snippetLine).StartLine, snd snippetLine))
// --------------------------------------------------------------------------------------
diff --git a/src/FSharp.CodeFormat/CommentFilter.fs b/src/FSharp.CodeFormat/CommentFilter.fs
index 0a9e5d303..52043ba92 100644
--- a/src/FSharp.CodeFormat/CommentFilter.fs
+++ b/src/FSharp.CodeFormat/CommentFilter.fs
@@ -11,6 +11,7 @@ open System.Web
open FSharp.Patterns
open FSharp.Collections
+open FSharp.Formatting.Common
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices
@@ -34,7 +35,7 @@ open Microsoft.FSharp.Compiler.SourceCodeServices
type Token = string * FSharpTokenInfo
type SnippetLine = Token list
-type IndexedSnippetLine = int * SnippetLine
+type IndexedSnippetLine = MarkdownRange * SnippetLine
type Snippet = IndexedSnippetLine list
type NamedSnippet = string * Snippet
@@ -49,7 +50,7 @@ let rec getSnippets (state:NamedSnippet option) (snippets:NamedSnippet list)
match source with
| [] -> snippets
| (line, tokens)::rest ->
- let text = lines.[line].Trim(), line
+ let text = lines.[line.StartLine].Trim(), line
match state, text with
// We're not inside a snippet and we found a beginning of one
diff --git a/src/FSharp.Formatting.Common/FSharp.Formatting.Common.fsproj b/src/FSharp.Formatting.Common/FSharp.Formatting.Common.fsproj
index 07fc84a1c..33e7125c9 100644
--- a/src/FSharp.Formatting.Common/FSharp.Formatting.Common.fsproj
+++ b/src/FSharp.Formatting.Common/FSharp.Formatting.Common.fsproj
@@ -69,6 +69,7 @@
Common\AssemblyInfo.fs
+
diff --git a/src/FSharp.Formatting.Common/Range.fs b/src/FSharp.Formatting.Common/Range.fs
new file mode 100644
index 000000000..bdb58b434
--- /dev/null
+++ b/src/FSharp.Formatting.Common/Range.fs
@@ -0,0 +1,11 @@
+namespace FSharp.Formatting.Common
+
+type MarkdownRange = { StartLine : int; StartColumn : int; EndLine : int; EndColumn : int }
+
+module MRange =
+ let Zero = { StartLine = 0; StartColumn = 0; EndLine = 0; EndColumn = 0 }
+
+ let MergeRanges (ranges:MarkdownRange list) =
+ let startRange = ranges |> List.minBy (fun r -> r.StartLine * 10 + r.StartColumn)
+ let endRange = ranges |> List.maxBy (fun r -> r.EndLine * 10 + r.EndColumn)
+ { StartLine = startRange.StartLine; StartColumn = startRange.StartColumn; EndLine = endRange.EndLine; EndColumn = endRange.EndColumn }
\ No newline at end of file
diff --git a/src/FSharp.Markdown/Main.fs b/src/FSharp.Markdown/Main.fs
index 21fc0bc8d..1b79fdda8 100644
--- a/src/FSharp.Markdown/Main.fs
+++ b/src/FSharp.Markdown/Main.fs
@@ -12,6 +12,7 @@ open System.Collections.Generic
open FSharp.Patterns
open FSharp.Markdown.Parser
open FSharp.Markdown.Html
+open FSharp.Formatting.Common
module private Utils =
/// Replace tabs with four spaces - tab will end at the
@@ -50,14 +51,14 @@ type Markdown =
[ let line = ref ""
let mutable lineNo = 1
while (line := reader.ReadLine(); line.Value <> null) do
- yield (line.Value, lineNo)
+ yield (line.Value, { StartLine = lineNo; StartColumn = 0; EndLine = lineNo; EndColumn = line.Value.Length })
lineNo <- lineNo + 1
if text.EndsWith(newline) then
- yield ("", lineNo) ]
+ yield ("", { StartLine = lineNo; StartColumn = 0; EndLine = lineNo; EndColumn = 0 }) ]
//|> Utils.replaceTabs 4
let links = Dictionary<_, _>()
//let (Lines.TrimBlank lines) = lines
- let ctx : ParsingContext = { Newline = newline; Links = links; CurrentRange = Some({ Line = 1 }) }
+ let ctx : ParsingContext = { Newline = newline; Links = links; CurrentRange = Some(MRange.Zero) }
let paragraphs =
lines
|> FSharp.Collections.List.skipWhile (fun (s, n) -> String.IsNullOrWhiteSpace s)
diff --git a/src/FSharp.Markdown/Markdown.fs b/src/FSharp.Markdown/Markdown.fs
index e2d760295..969477017 100644
--- a/src/FSharp.Markdown/Markdown.fs
+++ b/src/FSharp.Markdown/Markdown.fs
@@ -8,13 +8,12 @@ namespace FSharp.Markdown
open System
open System.IO
open System.Collections.Generic
+open FSharp.Formatting.Common
// --------------------------------------------------------------------------------------
// Definition of the Markdown format
// --------------------------------------------------------------------------------------
-type MarkdownRange = { Line : int }
-
/// A list kind can be `Ordered` or `Unordered` corresponding to `` and `
` elements
type MarkdownListKind =
| Ordered
@@ -40,8 +39,8 @@ type MarkdownSpan =
| DirectImage of body:string * linkAndTitle:(string * option) * range:MarkdownRange option
| IndirectImage of body:string * link:string * key:string * range:MarkdownRange option
| HardLineBreak of range:MarkdownRange option
- | LatexInlineMath of string * range:MarkdownRange option
- | LatexDisplayMath of string * range:MarkdownRange option
+ | LatexInlineMath of code:string * range:MarkdownRange option
+ | LatexDisplayMath of code:string * range:MarkdownRange option
| EmbedSpans of customSpans:MarkdownEmbedSpans * range:MarkdownRange option
/// A type alias for a list of `MarkdownSpan` values
diff --git a/src/FSharp.Markdown/MarkdownParser.fs b/src/FSharp.Markdown/MarkdownParser.fs
index ecac2f16d..0eb376db4 100644
--- a/src/FSharp.Markdown/MarkdownParser.fs
+++ b/src/FSharp.Markdown/MarkdownParser.fs
@@ -11,6 +11,7 @@ open System.Text.RegularExpressions
open FSharp.Patterns
open FSharp.Collections
+open FSharp.Formatting.Common
// --------------------------------------------------------------------------------------
// Parsing of Markdown - first part handles inline formatting
@@ -118,7 +119,7 @@ let (|AutoLink|_|) input =
let linkFor (scheme:string) =
let prefix = scheme.ToCharArray() |> Array.toList
match input with
- | List.DelimitedWith prefix [' '] (List.AsString link, rest) ->
+ | List.DelimitedWith prefix [' '] (List.AsString link, rest, s, e) ->
Some(scheme + link, ' '::rest)
| List.StartsWith prefix (List.AsString link) ->
Some(link, [])
@@ -156,14 +157,15 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
// Zero or one literals, depending whether there is some accumulated input
let accLiterals = Lazy.Create(fun () ->
- if List.isEmpty acc then []
- else [Literal(String(List.rev acc |> Array.ofList), ctx.CurrentRange)] )
+ if List.isEmpty acc then ([], ctx)
+ else ([Literal(String(List.rev acc |> Array.ofList), match ctx.CurrentRange with | Some(n) -> Some({ n with EndColumn = n.StartColumn + acc.Length }) | None -> None)], { ctx with CurrentRange = match ctx.CurrentRange with | Some(n) -> Some({ n with StartColumn = n.StartColumn + acc.Length }) | None -> None }) )
match input with
// Recognizes explicit line-break at the end of line
| ' '::' '::'\r'::'\n'::rest
| ' '::' '::('\n' | '\r')::rest ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
yield HardLineBreak(ctx.CurrentRange)
yield! parseChars [] rest ctx
@@ -178,64 +180,76 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
// Inline code delimited either using double `` or single `
// (if there are spaces around, then body can contain more backticks)
- | List.DelimitedWith ['`'; ' '] [' '; '`'] (body, rest)
- | List.DelimitedNTimes '`' (body, rest) ->
- yield! accLiterals.Value
- yield InlineCode(String(Array.ofList body).Trim(), ctx.CurrentRange)
+ | List.DelimitedWith ['`'; ' '] [' '; '`'] (body, rest, s, e)
+ | List.DelimitedNTimes '`' (body, rest, s, e) ->
+ let (value, ctx) = accLiterals.Value
+ yield! value
+ yield InlineCode(String(Array.ofList body).Trim(), match ctx.CurrentRange with Some(n) -> Some({ n with StartColumn = n.StartColumn + s; EndColumn = n.EndColumn - e }) | None -> None)
yield! parseChars [] rest ctx
// Display Latex inline math mode
| DelimitedLatexDisplayMath ['$';'$'] (body, rest) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
yield LatexDisplayMath(String(Array.ofList body).Trim(), ctx.CurrentRange)
yield! parseChars [] rest ctx
// Inline Latex inline math mode
| DelimitedLatexInlineMath ['$'] (body, rest) ->
- yield! accLiterals.Value
- yield LatexInlineMath(String(Array.ofList body).Trim(), ctx.CurrentRange)
+ let (value, ctx) = accLiterals.Value
+ let ctx = { ctx with CurrentRange = match ctx.CurrentRange with | Some(n) -> Some({ n with StartColumn = n.StartColumn + 1 }) | None -> None }
+ yield! value
+ let code = String(Array.ofList body).Trim()
+ yield LatexInlineMath(code, match ctx.CurrentRange with | Some(n) -> Some({ n with EndColumn = n.StartColumn + code.Length }) | None -> None)
yield! parseChars [] rest ctx
// Inline link wrapped as
- | List.DelimitedWith ['<'] ['>'] (List.AsString link, rest)
+ | List.DelimitedWith ['<'] ['>'] (List.AsString link, rest, s, e)
when Seq.forall (Char.IsWhiteSpace >> not) link && (link.Contains("@") || link.Contains("://")) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
yield DirectLink([Literal(link, ctx.CurrentRange)], (link, None), ctx.CurrentRange)
yield! parseChars [] rest ctx
// Not an inline link - leave as an inline HTML tag
- | List.DelimitedWith ['<'] ['>'] (tag, rest) ->
+ | List.DelimitedWith ['<'] ['>'] (tag, rest, s, e) ->
yield! parseChars ('>'::(List.rev tag) @ '<' :: acc) rest ctx
// Recognize direct link [foo](http://bar) or indirect link [foo][bar] or auto link http://bar
| DirectLink (body, link, rest) ->
- yield! accLiterals.Value
- let info = getLinkAndTitle (String(Array.ofList link), 0)
+ let (value, ctx) = accLiterals.Value
+ yield! value
+ let info = getLinkAndTitle (String(Array.ofList link), MRange.Zero)
yield DirectLink(parseChars [] body ctx |> List.ofSeq, info, ctx.CurrentRange)
yield! parseChars [] rest ctx
| IndirectLink(body, link, original, rest) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
let key = if String.IsNullOrEmpty(link) then String(body |> Array.ofSeq) else link
yield IndirectLink(parseChars [] body ctx |> List.ofSeq, original, key, ctx.CurrentRange)
yield! parseChars [] rest ctx
| AutoLink (link, rest) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
yield DirectLink([Literal(link, ctx.CurrentRange)], (link, None), ctx.CurrentRange)
yield! parseChars [] rest ctx
// Recognize image - this is a link prefixed with the '!' symbol
| '!'::DirectLink (body, link, rest) ->
- yield! accLiterals.Value
- yield DirectImage(String(Array.ofList body), getLinkAndTitle (String(Array.ofList link), 0), ctx.CurrentRange)
+ let (value, ctx) = accLiterals.Value
+ yield! value
+ yield DirectImage(String(Array.ofList body), getLinkAndTitle (String(Array.ofList link), MRange.Zero), ctx.CurrentRange)
yield! parseChars [] rest ctx
| '!'::IndirectLink(body, link, original, rest) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
let key = if String.IsNullOrEmpty(link) then String(body |> Array.ofSeq) else link
yield IndirectImage(String(Array.ofList body), original, key, ctx.CurrentRange)
yield! parseChars [] rest ctx
// Handle emphasised text
| Emphasised (body, f, rest) ->
- yield! accLiterals.Value
+ let (value, ctx) = accLiterals.Value
+ yield! value
let body = parseChars [] body ctx |> List.ofSeq
yield f(body, ctx.CurrentRange)
yield! parseChars [] rest ctx
@@ -247,10 +261,12 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
| x::xs ->
yield! parseChars (x::acc) xs ctx
| [] ->
- yield! accLiterals.Value }
+ let (value, ctx) = accLiterals.Value
+ yield! value }
/// Parse body of a paragraph into a list of Markdown inline spans
let parseSpans (StringPosition.TrimBoth(s, n)) ctx =
+ let ctx = { ctx with CurrentRange = Some(n) }
parseChars [] (s.ToCharArray() |> List.ofArray) ctx |> List.ofSeq
let rec trimSpaces numSpaces (s:string) =
@@ -265,11 +281,11 @@ let rec trimSpaces numSpaces (s:string) =
/// Recognizes heading, either prefixed with #s or followed by === or --- line
let (|Heading|_|) = function
- | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("=", 0))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("=", MRange.Zero))) :: rest ->
Some(1, header, rest)
- | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("-", 0))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("-", MRange.Zero))) :: rest ->
Some(2, header, rest)
- | StringPosition.StartsWithRepeated "#" (n, (header, ln)) :: rest ->
+ | StringPosition.StartsWithRepeated "#" (n, StringPosition.TrimBoth(header, ln)) :: rest ->
let header =
// Drop "##" at the end, but only when it is preceded by some whitespace
// (For example "## Hello F#" should be "Hello F#")
@@ -278,12 +294,12 @@ let (|Heading|_|) = function
if noHash.Length > 0 && Char.IsWhiteSpace(noHash.Chars(noHash.Length - 1))
then noHash else header
else header
- Some(n, (header.Trim(), ln), rest)
+ Some(n, (header, ln), rest)
| rest ->
None
/// Recognizes a horizontal rule written using *, _ or -
-let (|HorizontalRule|_|) (line:string, n:int) =
+let (|HorizontalRule|_|) (line:string, n:MarkdownRange) =
let rec loop ((h, a, u) as arg) i =
if (h >= 3 || a >= 3 || u >= 3) && i = line.Length then Some(line.[0])
elif i = line.Length then None
@@ -355,7 +371,7 @@ let (|FencedCodeBlock|_|) = function
/// Matches when the input starts with a number. Returns the
/// rest of the input, following the last number.
-let (|SkipSomeNumbers|_|) (input:string, n:int) =
+let (|SkipSomeNumbers|_|) (input:string, n:MarkdownRange) =
match List.ofSeq input with
| x::xs when Char.IsDigit x ->
let _, rest = List.partitionUntil (Char.IsDigit >> not) xs
@@ -370,7 +386,8 @@ let (|ListStart|_|) = function
// NOTE: a tab character after +, * or - isn't supported by the reference implementation
// (it will be parsed as paragraph for 0.22)
(StringPosition.StartsWithAny ["+ "; "* "; "- " (*; "+\t"; "*\t"; "-\t"*)] as item)) ->
- let li = ((fst item).Substring(2), snd item)
+ let range = snd item
+ let li = ((fst item).Substring(2), { range with StartColumn = range.StartColumn + 2 })
let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = li
let endIndent =
startIndent + 2 +
@@ -381,12 +398,12 @@ let (|ListStart|_|) = function
(startIndent, spaces,
(SkipSomeNumbers // read a number
(skipNumCount, '.' :: ' ' :: List.AsString item))) ->
- let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = (item, 0)
+ let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = (item, MRange.Zero)
let endIndent =
startIndent + 2 + skipNumCount +
// Handle case of code block
if startIndent2 >= 5 then 1 else startIndent2
- Some(Ordered, startIndent, endIndent, (item, 0))
+ Some(Ordered, startIndent, endIndent, (item, MRange.Zero))
| _ -> None
/// Splits input into lines until whitespace or starting of a list and the rest.
@@ -433,7 +450,7 @@ let (|ListItem|_|) prevSimple = function
yield (line.Trim(), n)
for (line, n) in more do
let trimmed = trimSpaces endIndent line
- yield (trimmed, n) ]
+ yield (trimmed, { n with StartColumn = n.StartColumn + line.Length - trimmed.Length }) ]
//let trimmed = line.TrimStart()
//if trimmed.Length >= line.Length - endIndent then yield trimmed
//else yield line.Substring(endIndent) ]
@@ -466,8 +483,8 @@ let rec pipeTableFindSplits (delim : char array) (line : char list) =
match line with
| DelimitedLatexDisplayMath [ '$'; '$' ] (body, rest) -> ptfs delim rest
| DelimitedLatexInlineMath [ '$' ] (body, rest) -> ptfs delim rest
- | List.DelimitedWith [ '`'; ' ' ] [ ' '; '`' ] (body, rest) -> ptfs delim rest
- | List.DelimitedNTimes '`' (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
| [] -> None
@@ -486,16 +503,16 @@ let rec pipeTableFindSplits (delim : char array) (line : char list) =
/// Recognizes alignment specified in the passed separator line.
let (|TableCellSeparator|_|) = function
- | StringPosition.StartsAndEndsWith (":", ":") (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignCenter)
- | StringPosition.StartsWith ":" (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignLeft)
- | StringPosition.StartsAndEndsWith ("", ":") (StringPosition.EqualsRepeated("-", 0)) -> Some(AlignRight)
- | StringPosition.EqualsRepeated("-", 0) -> Some(AlignDefault)
+ | StringPosition.StartsAndEndsWith (":", ":") (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignCenter)
+ | StringPosition.StartsWith ":" (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignLeft)
+ | StringPosition.StartsAndEndsWith ("", ":") (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignRight)
+ | StringPosition.EqualsRepeated("-", MRange.Zero) -> Some(AlignDefault)
| _ -> None
/// Recognizes row of pipe table.
/// The function takes number of expected columns and array of delimiters.
/// Returns list of strings between delimiters.
-let (|PipeTableRow|_|) (size : option) delimiters (line : string, n:int) =
+let (|PipeTableRow|_|) (size : option) delimiters (line : string, n:MarkdownRange) =
let parts =
pipeTableFindSplits delimiters (line.ToCharArray() |> Array.toList)
|> List.toArray
@@ -552,20 +569,20 @@ let (|PipeTableBlock|_|) input =
/// The function takes positions of grid columns (if known) and expected grid separator.
/// Passed function is used to check whether all parts within grid are valid.
/// Retuns tuple (position of grid columns, text between grid columns).
-let (|EmacsTableLine|_|) (grid:option) (c:char) (check:string * int -> bool) (line:string, n:int) =
+let (|EmacsTableLine|_|) (grid:option) (c:char) (check:string * MarkdownRange -> bool) (line:string, n:MarkdownRange) =
let p = if grid.IsSome then grid.Value else Array.FindAll([|0..line.Length - 1|], fun i -> line.[i] = c)
let n = p.Length - 1
if n < 2 || line.Length <= p.[n] || Array.exists (fun i -> line.[i] <> c) p then None
else
- let parts = [1..n] |> List.map (fun i -> line.Substring(p.[i - 1] + 1, p.[i] - p.[i - 1] - 1), i)
+ let parts = [1..n] |> List.map (fun i -> line.Substring(p.[i - 1] + 1, p.[i] - p.[i - 1] - 1), { StartLine = n; StartColumn = 0; EndLine = n; EndColumn = p.[i] - p.[i - 1] - 1 })
if List.forall check parts then Some(p, parts) else None
/// Recognizes emacs table
let (|EmacsTableBlock|_|) (input) =
- let isCellSep = StringPosition.(|EqualsRepeated|_|)("-", 0) >> Option.isSome
+ let isCellSep = StringPosition.(|EqualsRepeated|_|)("-", MRange.Zero) >> Option.isSome
let isAlignedCellSep = ( |TableCellSeparator|_| ) >> Option.isSome
- let isHeadCellSep = StringPosition.(|EqualsRepeated|_|)("=", 0) >> Option.isSome
- let isText (s:string, n:int) = true
+ let isHeadCellSep = StringPosition.(|EqualsRepeated|_|)("=", MRange.Zero) >> Option.isSome
+ let isText (s:string, n:MarkdownRange) = true
match input with
| (EmacsTableLine None '+' isAlignedCellSep (grid, parts)) :: rest ->
let alignments = List.choose ( |TableCellSeparator|_| ) parts
@@ -574,8 +591,8 @@ let (|EmacsTableBlock|_|) (input) =
// prevRow - content of the processed rows
// cur - list of paragraphs in the current row (list of empty lists after each separator line)
// flag indicates whether current row is empty (similar to List.forall (List.isEmpty) cur)
- let emptyCur = List.replicate<(string * int) list> (grid.Length - 1) []
- let rec loop flag headers (prevRows:(string * int) list list list) (cur:(string * int) list list) = function
+ let emptyCur = List.replicate<(string * MarkdownRange) list> (grid.Length - 1) []
+ let rec loop flag headers (prevRows:(string * MarkdownRange) list list list) (cur:(string * MarkdownRange) list list) = function
| (EmacsTableLine (Some grid) '|' isText (_, parts)) :: others ->
loop false headers prevRows (List.zip parts cur |> List.map (fun ((h, n), t) -> (h.TrimEnd(), n) :: t)) others
| (EmacsTableLine (Some grid) '+' isCellSep _) :: others ->
@@ -588,14 +605,16 @@ let (|EmacsTableBlock|_|) (input) =
| _ -> None
/// Recognizes a start of a blockquote
-let (|BlockquoteStart|_|) (line:string, n:int) =
+let (|BlockquoteStart|_|) (line:string, n:MarkdownRange) =
let regex =
"^ {0,3}" // Up to three leading spaces
+ ">" // Blockquote character
+ "\s?" // Maybe one whitespace character
+ "(.*)" // Capture everything else
let match' = Regex.Match(line, regex)
- if match'.Success then Some ((match'.Groups.Item(1)).Value, n)
+ if match'.Success then
+ let group = match'.Groups.Item(1)
+ Some (group.Value, { n with StartColumn = n.StartColumn + group.Index; EndColumn = n.StartColumn + group.Index + group.Length })
else None
/// Takes lines that belong to a continuing paragraph until
@@ -631,7 +650,7 @@ let (|LinesUntilBlockquoteEnds|) input =
/// starting with '>' until there is something else
let rec (|Blockquote|_|) = function
| EmptyBlockquote(Lines.TrimBlankStart rest) ->
- Some ([("", 0)], rest)
+ Some ([("", MRange.Zero)], rest)
| BlockquoteStart(line)::LinesUntilBlockquoteEnds(continued, Lines.TrimBlankStart rest) ->
let moreLines, rest =
match rest with
@@ -648,7 +667,7 @@ and (|EmptyBlockquote|_|) = function
| _ -> None
/// Recognizes Latex block - start with "$$$"
-let (|LatexBlock|_|) (lines:(string * int) list) = lines |> function
+let (|LatexBlock|_|) (lines:(string * MarkdownRange) list) = lines |> function
| (first, n)::rest when (first.TrimEnd()) = "$$$" -> rest |> function
| TakeParagraphLines(body, rest) -> Some(body, rest)
| _ -> None
@@ -666,10 +685,10 @@ let (|LinkDefinition|_|) = function
let updateCurrentRange lines =
match lines with
| [] -> None
- | (_, l)::_ -> Some({ Line = l })
+ | (_, l)::_ -> Some(l)
/// Parse a list of lines into a sequence of markdown paragraphs
-let rec parseParagraphs (ctx:ParsingContext) lines = seq {
+let rec parseParagraphs (ctx:ParsingContext) (lines:(string * MarkdownRange) list) = seq {
let ctx = { ctx with CurrentRange = updateCurrentRange lines }
match lines with
// Recognize various kinds of standard paragraphs
@@ -681,7 +700,7 @@ let rec parseParagraphs (ctx:ParsingContext) lines = seq {
yield CodeBlock(code |> String.concat ctx.Newline, langString, ignoredLine, ctx.CurrentRange)
yield! parseParagraphs ctx lines
| Blockquote(body, Lines.TrimBlankStart rest) ->
- yield QuotedBlock(parseParagraphs ctx (body @ [("", 0)]) |> List.ofSeq, ctx.CurrentRange)
+ yield QuotedBlock(parseParagraphs ctx (body @ [("", MRange.Zero)]) |> List.ofSeq, ctx.CurrentRange)
yield! parseParagraphs ctx rest
| EmacsTableBlock((headers, alignments, rows), Lines.TrimBlankStart rest)
| PipeTableBlock((headers, alignments, rows), Lines.TrimBlankStart rest) ->
@@ -715,18 +734,18 @@ let rec parseParagraphs (ctx:ParsingContext) lines = seq {
| [] -> []
// Turn tree into nested list definitions
- let rec formatTree (nodes:Tree list) =
+ let rec formatTree (nodes:Tree list) =
let kind = match nodes with Node((_, kind, _), _)::_ -> kind | _ -> Unordered
let items =
[ for (Node((simple, _, body), nested)) in nodes ->
[ if not simple then yield! parseParagraphs ctx body
- else yield Span(parseSpans(String.concat ctx.Newline (body |> List.map fst), 0) ctx, ctx.CurrentRange)
+ else yield Span(parseSpans(String.concat ctx.Newline (body |> List.map fst), body |> List.map snd |> MRange.MergeRanges) ctx, ctx.CurrentRange)
if nested <> [] then
yield formatTree nested ] ]
ListBlock(kind, items, ctx.CurrentRange)
// Make sure all items of the list have are either simple or not.
- let rec unifySimpleProperty (nodes:Tree list) =
+ let rec unifySimpleProperty (nodes:Tree list) =
let containsNonSimple =
tree |> Seq.exists (function
| Node ((false, _, _), _) -> true
@@ -750,7 +769,7 @@ let rec parseParagraphs (ctx:ParsingContext) lines = seq {
yield InlineBlock(all, ctx.CurrentRange)
yield! parseParagraphs ctx lines
| TakeParagraphLines(Lines.TrimParagraphLines lines, Lines.TrimBlankStart rest) ->
- yield Paragraph (parseSpans (String.concat ctx.Newline lines, 0) ctx, ctx.CurrentRange)
+ yield Paragraph (parseSpans (lines |> List.map fst |> String.concat ctx.Newline, lines |> List.map snd |> MRange.MergeRanges) ctx, ctx.CurrentRange)
yield! parseParagraphs ctx rest
| Lines.TrimBlankStart [] -> ()
diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs
index 16ad5d774..25f9fd2d6 100755
--- a/src/FSharp.MetadataFormat/Main.fs
+++ b/src/FSharp.MetadataFormat/Main.fs
@@ -546,7 +546,7 @@ module Reader =
Seq.iter (fun (x : XNode) ->
if x.NodeType = XmlNodeType.Text then
let text = (x :?> XText).Value
- match findCommand (text, 0) with
+ match findCommand (text, MRange.Zero) with
| Some (k,v) -> cmds.Add(k,v)
| None -> full.Append(text) |> ignore
elif x.NodeType = XmlNodeType.Element then
@@ -713,7 +713,7 @@ module Reader =
| null ->
dict[], (Comment.Create ("", el.Value, []))
| sum ->
- let lines = removeSpaces sum.Value |> Seq.map (fun s -> (s, 0))
+ let lines = removeSpaces sum.Value |> Seq.map (fun s -> (s, MRange.Zero))
let cmds = new System.Collections.Generic.Dictionary<_, _>()
if ctx.MarkdownComments then
diff --git a/tests/FSharp.Literate.Tests/Tests.fs b/tests/FSharp.Literate.Tests/Tests.fs
index 3ca09336c..75432718a 100644
--- a/tests/FSharp.Literate.Tests/Tests.fs
+++ b/tests/FSharp.Literate.Tests/Tests.fs
@@ -61,10 +61,10 @@ a
b""", __SOURCE_DIRECTORY__ @@ "Test.fsx")
//[/test]
- doc.Paragraphs |> shouldMatchPar (function Paragraph([Literal("a", Some({ Line = 2 }))], Some({ Line = 2 })) -> true | _ -> false)
- doc.Paragraphs |> shouldMatchPar (function Paragraph([Literal("b", Some({ Line = 6 }))], Some({ Line = 6 })) -> true | _ -> false)
+ doc.Paragraphs |> shouldMatchPar (function Paragraph([Literal("a", Some({ StartLine = 2 }))], Some({ StartLine = 2 })) -> true | _ -> false)
+ doc.Paragraphs |> shouldMatchPar (function Paragraph([Literal("b", Some({ StartLine = 6 }))], Some({ StartLine = 6 })) -> true | _ -> false)
doc.Paragraphs |> shouldMatchPar (function
- | EmbedParagraphs(:? LiterateParagraph as cd, Some({ Line = 4 })) ->
+ | EmbedParagraphs(:? LiterateParagraph as cd, Some({ StartLine = 4 })) ->
match cd with LanguageTaggedCode("csharp", text) -> text.Contains "magic" | _ -> false
| _ -> false)
@@ -82,7 +82,7 @@ let test = 42"""
doc.Paragraphs |> shouldMatchPar (function
| Matching.LiterateParagraph(FormattedCode(_)) -> true | _ -> false)
doc.Paragraphs |> shouldMatchPar (function
- | Paragraph([Strong([Literal("hello", Some({ Line = 1 }))], Some({ Line = 1 }))], Some({ Line = 1 })) -> true | _ -> false)
+ | Paragraph([Strong([Literal("hello", Some({ StartLine = 1 }))], Some({ StartLine = 1 }))], Some({ StartLine = 1 })) -> true | _ -> false)
[]
let ``Can parse heading on the same line as opnening comment (#147)`` () =
@@ -92,7 +92,7 @@ content *)
let test = 42"""
let doc = Literate.ParseScriptString(content, "C" @@ "A.fsx", getFormatAgent())
doc.Paragraphs |> shouldMatchPar (function
- | Heading(2, [Literal("Heading", Some({ Line = 1 }))], Some({ Line = 1 })) -> true | _ -> false)
+ | Heading(2, [Literal("Heading", Some({ StartLine = 1 }))], Some({ StartLine = 1 })) -> true | _ -> false)
[]
let ``Can parse and format markdown with F# snippet`` () =
@@ -105,7 +105,7 @@ let ``Can parse and format markdown with F# snippet`` () =
doc.Paragraphs |> shouldMatchPar (function
| Matching.LiterateParagraph(FormattedCode(_)) -> true | _ -> false)
doc.Paragraphs |> shouldMatchPar (function
- | Paragraph([Strong([Literal("hello", Some({ Line = 2 }))], Some({ Line = 2 }))], Some({ Line = 2 })) -> true | _ -> false)
+ | Paragraph([Strong([Literal("hello", Some({ StartLine = 2 }))], Some({ StartLine = 2 }))], Some({ StartLine = 2 })) -> true | _ -> false)
[]
let ``Can parse and format markdown with Github-flavoured F# snippet`` () =
@@ -120,7 +120,7 @@ let test = 42
doc.Paragraphs |> shouldMatchPar (function
| Matching.LiterateParagraph(FormattedCode(_)) -> true | _ -> false)
doc.Paragraphs |> shouldMatchPar (function
- | Paragraph([Strong([Literal("hello", Some({ Line = 2 }))], Some({ Line = 2 }))], Some({ Line = 2 })) -> true | _ -> false)
+ | Paragraph([Strong([Literal("hello", Some({ StartLine = 2 }))], Some({ StartLine = 2 }))], Some({ StartLine = 2 })) -> true | _ -> false)
[]
let ``Can parse and format markdown with Github-flavoured F# snippet starting and ending with empty lines`` () =
@@ -581,7 +581,7 @@ let ``Can format single snippet with label using literate parser`` () =
let add a b = a + b
// [/snippet]"""
let doc = Literate.ParseScriptString(source, "/somewhere/test.fsx", getFormatAgent())
- doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo", Some({ Line = 1 }))], Some({ Line = 1 })) -> true | _ -> false)
+ doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo", Some({ StartLine = 1 }))], Some({ StartLine = 1 })) -> true | _ -> false)
[]
@@ -594,8 +594,8 @@ let add a b = a + b
let mul a b = a * b
// [/snippet]"""
let doc = Literate.ParseScriptString(source, "/somewhere/test.fsx", getFormatAgent())
- doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo1", Some({ Line = 1 }))], Some({ Line = 1 })) -> true | _ -> false)
- doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo2", Some({ Line = 1 }))], Some({ Line = 1 })) -> true | _ -> false)
+ doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo1", Some({ StartLine = 1 }))], Some({ StartLine = 1 })) -> true | _ -> false)
+ doc.Paragraphs |> shouldMatchPar (function Heading(_, [Literal("demo2", Some({ StartLine = 1 }))], Some({ StartLine = 1 })) -> true | _ -> false)
[]
let ``Formatter does not crash on source that contains invalid string`` () =
diff --git a/tests/FSharp.Markdown.Tests/FSharp.Markdown.Tests.fsproj b/tests/FSharp.Markdown.Tests/FSharp.Markdown.Tests.fsproj
index cdebb63c3..01ed64dc6 100644
--- a/tests/FSharp.Markdown.Tests/FSharp.Markdown.Tests.fsproj
+++ b/tests/FSharp.Markdown.Tests/FSharp.Markdown.Tests.fsproj
@@ -95,6 +95,11 @@
-->
+
+ FSharp.Formatting.Common
+ {91bad90e-bf3b-4646-a1a7-1568f8f25075}
+ True
+ FSharp.Markdown{c44c1c05-599a-40dd-9590-465eab8960c5}
diff --git a/tests/FSharp.Markdown.Tests/Markdown.fs b/tests/FSharp.Markdown.Tests/Markdown.fs
index 093777632..8d90faf69 100644
--- a/tests/FSharp.Markdown.Tests/Markdown.fs
+++ b/tests/FSharp.Markdown.Tests/Markdown.fs
@@ -9,6 +9,7 @@ module FSharp.Markdown.Tests.Parsing
open FsUnit
open NUnit.Framework
open FSharp.Markdown
+open FSharp.Formatting.Common
let properNewLines (text: string) = text.Replace("\r\n", System.Environment.NewLine)
@@ -19,7 +20,7 @@ let shouldEqualNoWhiteSpace (x:string) (y:string) =
let ``Inline HTML tag containing 'at' is not turned into hyperlink`` () =
let doc = """hi""" |> Markdown.Parse
doc.Paragraphs
- |> shouldEqual [ Paragraph([Literal("""hi""", Some({ Line = 1 })) ], Some({ Line = 1 }))]
+ |> shouldEqual [ Paragraph([Literal("""hi""", Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 29 })) ], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 29 }))]
[]
let ``Encode '<' and '>' characters as HTML entities`` () =
@@ -36,8 +37,8 @@ Some more""" |> Markdown.Parse
doc.Paragraphs
|> shouldEqual [
- Heading(2, [Literal("Hello F#", Some({ Line = 2 }))], Some({ Line = 2 }))
- Paragraph([Literal("Some more", Some({ Line = 3 }))], Some({ Line = 3 }))]
+ Heading(2, [Literal("Hello F#", Some({ StartLine = 2; StartColumn = 3; EndLine = 2; EndColumn = 11 }))], Some({ StartLine = 2; StartColumn = 0; EndLine = 2; EndColumn = 11 }))
+ Paragraph([Literal("Some more", Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 9 }))], Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 9 }))]
[]
let ``Headings ending with spaces followed by # are parsed correctly`` () =
@@ -47,8 +48,8 @@ Some more""" |> Markdown.Parse
doc.Paragraphs
|> shouldEqual [
- Heading(2, [Literal("Hello", Some({ Line = 2 }))], Some({ Line = 2 }))
- Paragraph([Literal("Some more", Some({ Line = 3 }))], Some({ Line = 3 }))]
+ Heading(2, [Literal("Hello", Some({ StartLine = 2; StartColumn = 3; EndLine = 2; EndColumn = 8 }))], Some({ StartLine = 2; StartColumn = 0; EndLine = 2; EndColumn = 13 }))
+ Paragraph([Literal("Some more", Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 9 }))], Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 9 }))]
[]
let ``Should be able to create nested list item with two paragraphs`` () =
@@ -59,38 +60,38 @@ let ``Should be able to create nested list item with two paragraphs`` () =
c""" |> Markdown.Parse
let expectedBody =
- [ Paragraph([Literal("b", Some({ Line = 3 }))] , Some({ Line = 3 }))
- Paragraph([Literal("c", Some({ Line = 5 }))], Some({ Line = 5 })) ]
+ [ Paragraph([Literal("b", Some({ StartLine = 3; StartColumn = 4; EndLine = 3; EndColumn = 5 }))] , Some({ StartLine = 3; StartColumn = 4; EndLine = 3; EndColumn = 5 }))
+ Paragraph([Literal("c", Some({ StartLine = 5; StartColumn = 4; EndLine = 5; EndColumn = 5 }))], Some({ StartLine = 5; StartColumn = 4; EndLine = 5; EndColumn = 5 })) ]
match doc.Paragraphs.Head with
- | ListBlock(Unordered, [ [Span([Literal("a", Some({ Line = 2 }))], _); ListBlock(Unordered, [ body ], _)] ], _) ->
+ | ListBlock(Unordered, [ [Span([Literal("a", Some({ StartLine = 2; StartColumn = 2; EndLine = 2; EndColumn = 3 }))], _); ListBlock(Unordered, [ body ], _)] ], _) ->
body |> shouldEqual expectedBody
| _ -> Assert.Fail "Expected list block with a nested list block"
[]
let ``Can escape special characters such as "*" in emphasis`` () =
let doc = """*foo\*\*bar*""" |> Markdown.Parse
- let expected = Paragraph([Emphasis([Literal("foo**bar", Some({ Line = 1 }))], Some({ Line = 1 }))], Some({ Line = 1 }))
+ let expected = Paragraph([Emphasis([Literal("foo**bar", Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 8 }))], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 12 }))], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 12 }))
doc.Paragraphs.Head
|> shouldEqual expected
[]
let ``Can escape special characters in LaTex inline math`` () =
let doc = """test \$ is: $foo\$\$bar<>\$\&\%\$\#\_\{\}$""" |> Markdown.Parse
- let expected = Paragraph([Literal("test $ is: ", Some({ Line = 1 })); LatexInlineMath("foo\$\$bar<>\$\&\%\$\#\_\{\}", Some({ Line = 1 }))], Some({ Line = 1 }))
+ let expected = Paragraph([Literal("test $ is: ", Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 11 })); LatexInlineMath("foo\$\$bar<>\$\&\%\$\#\_\{\}", Some({ StartLine = 1; StartColumn = 12; EndLine = 1; EndColumn = 40 }))], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 42 }))
doc.Paragraphs.Head
|> shouldEqual expected
[]
let ``Test special character _ in LaTex inline math`` () =
let doc = """$\bigcap_{x \in A} p_{x}A$""" |> Markdown.Parse
- let expected = Paragraph([ LatexInlineMath("\\bigcap_{x \\in A} p_{x}A", Some({ Line = 1 })) ], Some({ Line = 1 }))
+ let expected = Paragraph([ LatexInlineMath("\\bigcap_{x \\in A} p_{x}A", Some({ StartLine = 1; StartColumn = 1; EndLine = 1; EndColumn = 25 })) ], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 26 }))
doc.Paragraphs.Head
|> shouldEqual expected
[]
let ``Inline code can contain backticks when wrapped with spaces`` () =
let doc = """` ``h`` `""" |> Markdown.Parse
- let expected = Paragraph([InlineCode("``h``", Some({ Line = 1 }))], Some({ Line = 1 }))
+ let expected = Paragraph([InlineCode("``h``", Some({ StartLine = 1; StartColumn = 2; EndLine = 1; EndColumn = 7 }))], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 9 }))
doc.Paragraphs.Head
|> shouldEqual expected
@@ -284,7 +285,7 @@ let ``Transform horizontal rules correctly``() =
let doc = "* * *\r\n\r\n***\r\n\r\n*****\r\n\r\n- - -\r\n\r\n---------------------------------------\r\n\r\n";
let expected = "\r\n\r\n\r\n\r\n\r\n" |> properNewLines
Markdown.Parse(doc).Paragraphs
- |> shouldEqual [ HorizontalRule('*', Some({ Line = 1 })); HorizontalRule('*', Some({ Line = 3 })); HorizontalRule('*', Some({ Line = 5 })); HorizontalRule('-', Some({ Line = 7 })); HorizontalRule('-', Some({ Line = 9 })) ]
+ |> shouldEqual [ HorizontalRule('*', Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 5 })); HorizontalRule('*', Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 3 })); HorizontalRule('*', Some({ StartLine = 5; StartColumn = 0; EndLine = 5; EndColumn = 5 })); HorizontalRule('-', Some({ StartLine = 7; StartColumn = 0; EndLine = 7; EndColumn = 5 })); HorizontalRule('-', Some({ StartLine = 9; StartColumn = 0; EndLine = 9; EndColumn = 39 })) ]
Markdown.TransformHtml doc
|> shouldEqual expected
@@ -326,8 +327,8 @@ let ``Transform tables with delimiters in code or math correctly``() =
let ``Parse empty blockquote followed by content``() =
let doc = ">
a"
- let expected = [ QuotedBlock([], Some({ Line = 1 }))
- Paragraph([ Literal("a", Some({ Line = 2 })) ], Some({ Line = 2 })) ]
+ let expected = [ QuotedBlock([], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 1 }))
+ Paragraph([ Literal("a", Some({ StartLine = 2; StartColumn = 0; EndLine = 2; EndColumn = 1 })) ], Some({ StartLine = 2; StartColumn = 0; EndLine = 2; EndColumn = 1 })) ]
(Markdown.Parse doc).Paragraphs
|> shouldEqual expected
@@ -337,8 +338,8 @@ let ``Parse blockquote teriminated by empty blockquote line and followed by cont
let doc = ">a
>
a"
- let expected = [ QuotedBlock([ Paragraph([ Literal("a", Some({ Line = 1 })) ], Some({ Line = 1 })) ], Some({ Line = 1 }))
- Paragraph([ Literal("a", Some({ Line = 3 })) ], Some({ Line = 3 })) ]
+ let expected = [ QuotedBlock([ Paragraph([ Literal("a", Some({ StartLine = 1; StartColumn = 1; EndLine = 1; EndColumn = 2 })) ], Some({ StartLine = 1; StartColumn = 1; EndLine = 1; EndColumn = 2 })) ], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 2 }))
+ Paragraph([ Literal("a", Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 1 })) ], Some({ StartLine = 3; StartColumn = 0; EndLine = 3; EndColumn = 1 })) ]
(Markdown.Parse doc).Paragraphs
|> shouldEqual expected
@@ -346,7 +347,7 @@ a"
[]
let ``Parse blockquote with three leading spaces``() =
let doc = " >a"
- let expected = [ QuotedBlock([ Paragraph([ Literal("a", Some({ Line = 1 })) ], Some({ Line = 1 })) ], Some({ Line = 1 })) ]
+ let expected = [ QuotedBlock([ Paragraph([ Literal("a", Some({ StartLine = 1; StartColumn = 4; EndLine = 1; EndColumn = 5 })) ], Some({ StartLine = 1; StartColumn = 4; EndLine = 1; EndColumn = 5 })) ], Some({ StartLine = 1; StartColumn = 0; EndLine = 1; EndColumn = 5 })) ]
(Markdown.Parse doc).Paragraphs
|> shouldEqual expected
\ No newline at end of file
From 2b6f7bb2535cd1445d33ef8064600a07ba1e82f4 Mon Sep 17 00:00:00 2001
From: Avi Avni
Date: Tue, 12 Jul 2016 09:22:25 +0300
Subject: [PATCH 6/6] fixes from tomas code review
---
docs/content/markdown.fsx | 2 +-
src/FSharp.Formatting.Common/Range.fs | 3 +-
src/FSharp.Markdown/HtmlFormatting.fs | 2 +-
src/FSharp.Markdown/LatexFormatting.fs | 2 +-
src/FSharp.Markdown/Main.fs | 2 +-
src/FSharp.Markdown/Markdown.fs | 18 +++++------
src/FSharp.Markdown/MarkdownParser.fs | 45 +++++++++++++++-----------
src/FSharp.MetadataFormat/Main.fs | 4 +--
8 files changed, 44 insertions(+), 34 deletions(-)
diff --git a/docs/content/markdown.fsx b/docs/content/markdown.fsx
index 133127bda..3818c6bcd 100644
--- a/docs/content/markdown.fsx
+++ b/docs/content/markdown.fsx
@@ -67,7 +67,7 @@ The following snippet prints the heading of the document:
// Iterate over all the paragraph elements
for par in parsed.Paragraphs do
match par with
- | Heading(1, [Literal(text, _)], _) ->
+ | Heading(size=1; body=[Literal(text=text)]) ->
// Recognize heading that has a simple content
// containing just a literal (no other formatting)
printfn "%s" text
diff --git a/src/FSharp.Formatting.Common/Range.fs b/src/FSharp.Formatting.Common/Range.fs
index bdb58b434..f0946edad 100644
--- a/src/FSharp.Formatting.Common/Range.fs
+++ b/src/FSharp.Formatting.Common/Range.fs
@@ -2,7 +2,8 @@
type MarkdownRange = { StartLine : int; StartColumn : int; EndLine : int; EndColumn : int }
-module MRange =
+[]
+module MarkdownRange =
let Zero = { StartLine = 0; StartColumn = 0; EndLine = 0; EndColumn = 0 }
let MergeRanges (ranges:MarkdownRange list) =
diff --git a/src/FSharp.Markdown/HtmlFormatting.fs b/src/FSharp.Markdown/HtmlFormatting.fs
index b84808ee5..45badc162 100644
--- a/src/FSharp.Markdown/HtmlFormatting.fs
+++ b/src/FSharp.Markdown/HtmlFormatting.fs
@@ -98,7 +98,7 @@ let rec formatSpan (ctx:FormattingContext) = function
ctx.Writer.Write(original)
| IndirectImage(body, _, LookupKey ctx.Links (link, title), _)
- | DirectImage(body, (link, title), _) ->
+ | DirectImage(body, link, title, _) ->
ctx.Writer.Write("
// Use the technique introduced at
// http://stackoverflow.com/q/14014827
diff --git a/src/FSharp.Markdown/Main.fs b/src/FSharp.Markdown/Main.fs
index 1b79fdda8..155a729ce 100644
--- a/src/FSharp.Markdown/Main.fs
+++ b/src/FSharp.Markdown/Main.fs
@@ -58,7 +58,7 @@ type Markdown =
//|> Utils.replaceTabs 4
let links = Dictionary<_, _>()
//let (Lines.TrimBlank lines) = lines
- let ctx : ParsingContext = { Newline = newline; Links = links; CurrentRange = Some(MRange.Zero) }
+ let ctx : ParsingContext = { Newline = newline; Links = links; CurrentRange = Some(MarkdownRange.Zero) }
let paragraphs =
lines
|> FSharp.Collections.List.skipWhile (fun (s, n) -> String.IsNullOrWhiteSpace s)
diff --git a/src/FSharp.Markdown/Markdown.fs b/src/FSharp.Markdown/Markdown.fs
index 969477017..11e06f149 100644
--- a/src/FSharp.Markdown/Markdown.fs
+++ b/src/FSharp.Markdown/Markdown.fs
@@ -31,12 +31,12 @@ type MarkdownColumnAlignment =
type MarkdownSpan =
| Literal of text:string * range:MarkdownRange option
| InlineCode of code:string * range:MarkdownRange option
- | Strong of spans:MarkdownSpans * range:MarkdownRange option
- | Emphasis of spans:MarkdownSpans * range:MarkdownRange option
+ | Strong of body:MarkdownSpans * range:MarkdownRange option
+ | Emphasis of body:MarkdownSpans * range:MarkdownRange option
| AnchorLink of link:string * range:MarkdownRange option
- | DirectLink of spans:MarkdownSpans * linkAndTitle:(string * option) * range:MarkdownRange option
- | IndirectLink of spans:MarkdownSpans * link:string * key:string * range:MarkdownRange option
- | DirectImage of body:string * linkAndTitle:(string * option) * range:MarkdownRange option
+ | DirectLink of body:MarkdownSpans * linkAndTitle:(string * option) * range:MarkdownRange option
+ | IndirectLink of body:MarkdownSpans * link:string * key:string * range:MarkdownRange option
+ | DirectImage of body:string * link:string * title:option * range:MarkdownRange option
| IndirectImage of body:string * link:string * key:string * range:MarkdownRange option
| HardLineBreak of range:MarkdownRange option
| LatexInlineMath of code:string * range:MarkdownRange option
@@ -55,15 +55,15 @@ and MarkdownEmbedSpans =
/// Paragraphs are headings, inline paragraphs, code blocks, lists, quotations, tables and
/// also embedded LaTeX blocks.
type MarkdownParagraph =
- | Heading of n:int * spans:MarkdownSpans * range:MarkdownRange option
- | Paragraph of spans:MarkdownSpans * range:MarkdownRange option
+ | Heading of size:int * body:MarkdownSpans * range:MarkdownRange option
+ | Paragraph of body:MarkdownSpans * range:MarkdownRange option
| CodeBlock of code:string * language:string * ignoredLine:string * range:MarkdownRange option
| InlineBlock of code:string * range:MarkdownRange option
| ListBlock of kind:MarkdownListKind * items:list * range:MarkdownRange option
| QuotedBlock of paragraphs:MarkdownParagraphs * range:MarkdownRange option
- | Span of spans:MarkdownSpans * range:MarkdownRange option
+ | Span of body:MarkdownSpans * range:MarkdownRange option
| LatexBlock of body:list * range:MarkdownRange option
- | HorizontalRule of c:char * range:MarkdownRange option
+ | HorizontalRule of character:char * range:MarkdownRange option
| TableBlock of headers:option * alignments:list * rows:list * range:MarkdownRange option
| EmbedParagraphs of customParagraphs:MarkdownEmbedParagraphs * range:MarkdownRange option
diff --git a/src/FSharp.Markdown/MarkdownParser.fs b/src/FSharp.Markdown/MarkdownParser.fs
index 0eb376db4..97a808d25 100644
--- a/src/FSharp.Markdown/MarkdownParser.fs
+++ b/src/FSharp.Markdown/MarkdownParser.fs
@@ -155,10 +155,18 @@ type ParsingContext =
/// Parses a body of a paragraph and recognizes all inline tags.
let rec parseChars acc input (ctx:ParsingContext) = seq {
- // Zero or one literals, depending whether there is some accumulated input
+ // Zero or one literals, depending whether there is some accumulated input and update the ctx
let accLiterals = Lazy.Create(fun () ->
if List.isEmpty acc then ([], ctx)
- else ([Literal(String(List.rev acc |> Array.ofList), match ctx.CurrentRange with | Some(n) -> Some({ n with EndColumn = n.StartColumn + acc.Length }) | None -> None)], { ctx with CurrentRange = match ctx.CurrentRange with | Some(n) -> Some({ n with StartColumn = n.StartColumn + acc.Length }) | None -> None }) )
+ else
+ let range = match ctx.CurrentRange with
+ | Some(n) -> Some({ n with EndColumn = n.StartColumn + acc.Length })
+ | None -> None
+ let ctx = { ctx with CurrentRange = match ctx.CurrentRange with
+ | Some(n) -> Some({ n with StartColumn = n.StartColumn + acc.Length })
+ | None -> None }
+ let text = String(List.rev acc |> Array.ofList)
+ ([Literal(text, range)], ctx) )
match input with
// Recognizes explicit line-break at the end of line
@@ -218,7 +226,7 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
| DirectLink (body, link, rest) ->
let (value, ctx) = accLiterals.Value
yield! value
- let info = getLinkAndTitle (String(Array.ofList link), MRange.Zero)
+ let info = getLinkAndTitle (String(Array.ofList link), MarkdownRange.Zero)
yield DirectLink(parseChars [] body ctx |> List.ofSeq, info, ctx.CurrentRange)
yield! parseChars [] rest ctx
| IndirectLink(body, link, original, rest) ->
@@ -237,7 +245,8 @@ let rec parseChars acc input (ctx:ParsingContext) = seq {
| '!'::DirectLink (body, link, rest) ->
let (value, ctx) = accLiterals.Value
yield! value
- yield DirectImage(String(Array.ofList body), getLinkAndTitle (String(Array.ofList link), MRange.Zero), ctx.CurrentRange)
+ let link, title = getLinkAndTitle (String(Array.ofList link), MarkdownRange.Zero)
+ yield DirectImage(String(Array.ofList body), link, title, ctx.CurrentRange)
yield! parseChars [] rest ctx
| '!'::IndirectLink(body, link, original, rest) ->
let (value, ctx) = accLiterals.Value
@@ -281,9 +290,9 @@ let rec trimSpaces numSpaces (s:string) =
/// Recognizes heading, either prefixed with #s or followed by === or --- line
let (|Heading|_|) = function
- | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("=", MRange.Zero))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("=", MarkdownRange.Zero))) :: rest ->
Some(1, header, rest)
- | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("-", MRange.Zero))) :: rest ->
+ | (StringPosition.TrimBoth header) :: (StringPosition.TrimEnd (StringPosition.EqualsRepeated("-", MarkdownRange.Zero))) :: rest ->
Some(2, header, rest)
| StringPosition.StartsWithRepeated "#" (n, StringPosition.TrimBoth(header, ln)) :: rest ->
let header =
@@ -398,12 +407,12 @@ let (|ListStart|_|) = function
(startIndent, spaces,
(SkipSomeNumbers // read a number
(skipNumCount, '.' :: ' ' :: List.AsString item))) ->
- let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = (item, MRange.Zero)
+ let (StringPosition.TrimStartAndCount (startIndent2, spaces2, _)) = (item, MarkdownRange.Zero)
let endIndent =
startIndent + 2 + skipNumCount +
// Handle case of code block
if startIndent2 >= 5 then 1 else startIndent2
- Some(Ordered, startIndent, endIndent, (item, MRange.Zero))
+ Some(Ordered, startIndent, endIndent, (item, MarkdownRange.Zero))
| _ -> None
/// Splits input into lines until whitespace or starting of a list and the rest.
@@ -503,10 +512,10 @@ let rec pipeTableFindSplits (delim : char array) (line : char list) =
/// Recognizes alignment specified in the passed separator line.
let (|TableCellSeparator|_|) = function
- | StringPosition.StartsAndEndsWith (":", ":") (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignCenter)
- | StringPosition.StartsWith ":" (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignLeft)
- | StringPosition.StartsAndEndsWith ("", ":") (StringPosition.EqualsRepeated("-", MRange.Zero)) -> Some(AlignRight)
- | StringPosition.EqualsRepeated("-", MRange.Zero) -> Some(AlignDefault)
+ | StringPosition.StartsAndEndsWith (":", ":") (StringPosition.EqualsRepeated("-", MarkdownRange.Zero)) -> Some(AlignCenter)
+ | StringPosition.StartsWith ":" (StringPosition.EqualsRepeated("-", MarkdownRange.Zero)) -> Some(AlignLeft)
+ | StringPosition.StartsAndEndsWith ("", ":") (StringPosition.EqualsRepeated("-", MarkdownRange.Zero)) -> Some(AlignRight)
+ | StringPosition.EqualsRepeated("-", MarkdownRange.Zero) -> Some(AlignDefault)
| _ -> None
/// Recognizes row of pipe table.
@@ -579,9 +588,9 @@ let (|EmacsTableLine|_|) (grid:option) (c:char) (check:string * Markdown
/// Recognizes emacs table
let (|EmacsTableBlock|_|) (input) =
- let isCellSep = StringPosition.(|EqualsRepeated|_|)("-", MRange.Zero) >> Option.isSome
+ let isCellSep = StringPosition.(|EqualsRepeated|_|)("-", MarkdownRange.Zero) >> Option.isSome
let isAlignedCellSep = ( |TableCellSeparator|_| ) >> Option.isSome
- let isHeadCellSep = StringPosition.(|EqualsRepeated|_|)("=", MRange.Zero) >> Option.isSome
+ let isHeadCellSep = StringPosition.(|EqualsRepeated|_|)("=", MarkdownRange.Zero) >> Option.isSome
let isText (s:string, n:MarkdownRange) = true
match input with
| (EmacsTableLine None '+' isAlignedCellSep (grid, parts)) :: rest ->
@@ -650,7 +659,7 @@ let (|LinesUntilBlockquoteEnds|) input =
/// starting with '>' until there is something else
let rec (|Blockquote|_|) = function
| EmptyBlockquote(Lines.TrimBlankStart rest) ->
- Some ([("", MRange.Zero)], rest)
+ Some ([("", MarkdownRange.Zero)], rest)
| BlockquoteStart(line)::LinesUntilBlockquoteEnds(continued, Lines.TrimBlankStart rest) ->
let moreLines, rest =
match rest with
@@ -700,7 +709,7 @@ let rec parseParagraphs (ctx:ParsingContext) (lines:(string * MarkdownRange) lis
yield CodeBlock(code |> String.concat ctx.Newline, langString, ignoredLine, ctx.CurrentRange)
yield! parseParagraphs ctx lines
| Blockquote(body, Lines.TrimBlankStart rest) ->
- yield QuotedBlock(parseParagraphs ctx (body @ [("", MRange.Zero)]) |> List.ofSeq, ctx.CurrentRange)
+ yield QuotedBlock(parseParagraphs ctx (body @ [("", MarkdownRange.Zero)]) |> List.ofSeq, ctx.CurrentRange)
yield! parseParagraphs ctx rest
| EmacsTableBlock((headers, alignments, rows), Lines.TrimBlankStart rest)
| PipeTableBlock((headers, alignments, rows), Lines.TrimBlankStart rest) ->
@@ -739,7 +748,7 @@ let rec parseParagraphs (ctx:ParsingContext) (lines:(string * MarkdownRange) lis
let items =
[ for (Node((simple, _, body), nested)) in nodes ->
[ if not simple then yield! parseParagraphs ctx body
- else yield Span(parseSpans(String.concat ctx.Newline (body |> List.map fst), body |> List.map snd |> MRange.MergeRanges) ctx, ctx.CurrentRange)
+ else yield Span(parseSpans(String.concat ctx.Newline (body |> List.map fst), body |> List.map snd |> MarkdownRange.MergeRanges) ctx, ctx.CurrentRange)
if nested <> [] then
yield formatTree nested ] ]
ListBlock(kind, items, ctx.CurrentRange)
@@ -769,7 +778,7 @@ let rec parseParagraphs (ctx:ParsingContext) (lines:(string * MarkdownRange) lis
yield InlineBlock(all, ctx.CurrentRange)
yield! parseParagraphs ctx lines
| TakeParagraphLines(Lines.TrimParagraphLines lines, Lines.TrimBlankStart rest) ->
- yield Paragraph (parseSpans (lines |> List.map fst |> String.concat ctx.Newline, lines |> List.map snd |> MRange.MergeRanges) ctx, ctx.CurrentRange)
+ yield Paragraph (parseSpans (lines |> List.map fst |> String.concat ctx.Newline, lines |> List.map snd |> MarkdownRange.MergeRanges) ctx, ctx.CurrentRange)
yield! parseParagraphs ctx rest
| Lines.TrimBlankStart [] -> ()
diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs
index 25f9fd2d6..ce1d74f99 100755
--- a/src/FSharp.MetadataFormat/Main.fs
+++ b/src/FSharp.MetadataFormat/Main.fs
@@ -546,7 +546,7 @@ module Reader =
Seq.iter (fun (x : XNode) ->
if x.NodeType = XmlNodeType.Text then
let text = (x :?> XText).Value
- match findCommand (text, MRange.Zero) with
+ match findCommand (text, MarkdownRange.Zero) with
| Some (k,v) -> cmds.Add(k,v)
| None -> full.Append(text) |> ignore
elif x.NodeType = XmlNodeType.Element then
@@ -713,7 +713,7 @@ module Reader =
| null ->
dict[], (Comment.Create ("", el.Value, []))
| sum ->
- let lines = removeSpaces sum.Value |> Seq.map (fun s -> (s, MRange.Zero))
+ let lines = removeSpaces sum.Value |> Seq.map (fun s -> (s, MarkdownRange.Zero))
let cmds = new System.Collections.Generic.Dictionary<_, _>()
if ctx.MarkdownComments then