Skip to content

Commit ecceb15

Browse files
authored
Merge pull request #11833 from dotty-staging/scaladoc/wiki-code-for-md
Scaladoc: add Wiki code blocks to Md syntax
2 parents f8bd01c + 5062997 commit ecceb15

File tree

3 files changed

+179
-4
lines changed

3 files changed

+179
-4
lines changed

scaladoc-testcases/src/tests/tests.scala

+15
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,22 @@ package tests
2020
* ```scala
2121
* is.an("actual code block")
2222
* with.multiple("lines")
23+
* """
24+
* {{{
25+
* and an embedded Wiki code block for good measure
26+
* }}}
27+
* """
28+
* ```
29+
*
30+
* While
31+
* {{{
32+
* this.is("a Wiki code block")
33+
* """
2334
* ```
35+
* with an embedded Md code block for good measure
36+
* ```
37+
* """
38+
* }}}
2439
*
2540
* And this
2641
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package dotty.tools.scaladoc.parsers
2+
3+
import com.vladsch.flexmark.ast._
4+
import com.vladsch.flexmark.parser.Parser
5+
import com.vladsch.flexmark.parser.core._
6+
import com.vladsch.flexmark.parser.block._
7+
import com.vladsch.flexmark.util.ast.Block
8+
import com.vladsch.flexmark.util.ast.BlockContent
9+
import com.vladsch.flexmark.util.options.DataHolder
10+
import com.vladsch.flexmark.util.sequence.BasedSequence
11+
import com.vladsch.flexmark.util.sequence.SegmentedSequence
12+
13+
import java.{util => ju}
14+
import ju.regex.Matcher
15+
import ju.regex.Pattern
16+
17+
18+
/** Copied from FencedCodeBlockParser. */
19+
object WikiCodeBlockParser {
20+
private val OPENING_FENCE = Pattern.compile("^\\{{3}")
21+
private val CLOSING_FENCE = Pattern.compile("^(\\}{3})(?=[ \t]*$)$")
22+
23+
class Factory extends CustomBlockParserFactory {
24+
override def getAfterDependents =
25+
new ju.HashSet[Class[_ <: CustomBlockParserFactory]](ju.Arrays.asList(
26+
classOf[BlockQuoteParser.Factory],
27+
classOf[HeadingParser.Factory],
28+
//FencedCodeBlockParser.Factory.class,
29+
//HtmlBlockParser.Factory.class,
30+
//ThematicBreakParser.Factory.class,
31+
//ListBlockParser.Factory.class,
32+
//IndentedCodeBlockParser.Factory.class
33+
))
34+
35+
override def getBeforeDependents =
36+
new ju.HashSet[Class[_ <: CustomBlockParserFactory]](ju.Arrays.asList(
37+
//BlockQuoteParser.Factory.class,
38+
//HeadingParser.Factory.class,
39+
//FencedCodeBlockParser.Factory.class,
40+
classOf[HtmlBlockParser.Factory],
41+
classOf[ThematicBreakParser.Factory],
42+
classOf[ListBlockParser.Factory],
43+
classOf[IndentedCodeBlockParser.Factory],
44+
))
45+
46+
override def affectsGlobalScope = false
47+
48+
override def create(options: DataHolder) =
49+
new WikiCodeBlockParser.BlockFactory(options)
50+
}
51+
52+
private[WikiCodeBlockParser] class BlockFactory (val options: DataHolder)
53+
extends AbstractBlockParserFactory(options) {
54+
def tryStart(state: ParserState, matchedBlockParser: MatchedBlockParser): BlockStart = {
55+
val nextNonSpace = state.getNextNonSpaceIndex
56+
val line = state.getLine
57+
var matcher: Matcher = null
58+
if state.getIndent < 4 then {
59+
val trySequence = line.subSequence(nextNonSpace, line.length)
60+
matcher = OPENING_FENCE.matcher(trySequence)
61+
if matcher.find then {
62+
val fenceLength = matcher.group(0).length
63+
val blockParser =
64+
new WikiCodeBlockParser(state.getProperties, fenceLength, state.getIndent, nextNonSpace)
65+
blockParser.block.setOpeningMarker(trySequence.subSequence(0, fenceLength))
66+
return BlockStart.of(blockParser).atIndex(nextNonSpace + fenceLength)
67+
}
68+
}
69+
BlockStart.none
70+
}
71+
}
72+
}
73+
74+
/** Copied from FencedCodeBlockParser. */
75+
class WikiCodeBlockParser(
76+
options: DataHolder,
77+
var fenceLength: Int,
78+
private var fenceIndent: Int,
79+
private var fenceMarkerIndent: Int
80+
) extends AbstractBlockParser {
81+
82+
this.fenceMarkerIndent = fenceIndent + fenceMarkerIndent
83+
84+
final private val block = new FencedCodeBlock()
85+
private var content = new BlockContent
86+
private val codeContentBlock = options.get(Parser.FENCED_CODE_CONTENT_BLOCK)
87+
88+
def getBlock: Block = block
89+
def getFenceIndent: Int = fenceIndent
90+
def getFenceMarkerIndent: Int = fenceMarkerIndent
91+
def tryContinue(state: ParserState): BlockContinue = {
92+
val nextNonSpace = state.getNextNonSpaceIndex
93+
var newIndex = state.getIndex
94+
val line = state.getLine
95+
var matcher: Matcher = null
96+
val matches =
97+
state.getIndent <= 3
98+
&& nextNonSpace < line.length
99+
100+
if matches then {
101+
val trySequence = line.subSequence(nextNonSpace, line.length)
102+
matcher = WikiCodeBlockParser.CLOSING_FENCE.matcher(trySequence)
103+
if matcher.find then {
104+
val foundFenceLength = matcher.group(0).length
105+
if (foundFenceLength >= fenceLength) { // closing fence - we're at end of line, so we can finalize now
106+
block.setClosingMarker(trySequence.subSequence(0, foundFenceLength))
107+
return BlockContinue.finished
108+
}
109+
}
110+
}
111+
// skip optional spaces of fence indent
112+
var i = fenceIndent
113+
while ({
114+
i > 0 && newIndex < line.length && line.charAt(newIndex) == ' '
115+
}) do {
116+
newIndex += 1
117+
i -= 1
118+
}
119+
BlockContinue.atIndex(newIndex)
120+
}
121+
122+
override def addLine(state: ParserState, line: BasedSequence): Unit = {
123+
content.add(line, state.getIndent)
124+
}
125+
126+
override def isPropagatingLastBlankLine(lastMatchedBlockParser: BlockParser) = false
127+
128+
override def closeBlock(state: ParserState): Unit = { // first line, if not blank, has the info string
129+
val lines = content.getLines
130+
if (lines.size > 0) {
131+
val info = lines.get(0)
132+
if (!info.isBlank) block.setInfo(info.trim)
133+
val chars = content.getSpanningChars
134+
val spanningChars = chars.baseSubSequence(chars.getStartOffset, lines.get(0).getEndOffset)
135+
if (lines.size > 1) { // have more lines
136+
val segments = lines.subList(1, lines.size)
137+
block.setContent(spanningChars, segments)
138+
if this.codeContentBlock then {
139+
val codeBlock = new CodeBlock()
140+
codeBlock.setContent(segments)
141+
codeBlock.setCharsFromContent
142+
block.appendChild(codeBlock)
143+
} else {
144+
val codeBlock = new Text(SegmentedSequence.of(segments))
145+
block.appendChild(codeBlock)
146+
}
147+
}
148+
else block.setContent(spanningChars, BasedSequence.EMPTY_LIST)
149+
}
150+
else block.setContent(content)
151+
block.setCharsFromContent
152+
content = null
153+
}
154+
}
155+

scaladoc/src/dotty/tools/scaladoc/tasty/comments/MarkdownParser.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,19 @@ object MarkdownParser {
4444

4545
val markdownOptions: DataHolder = mkMarkdownOptions(Seq(WikiLinkExtension.create()))
4646

47-
private val parser = Parser.builder(markdownOptions).build()
48-
4947
val RENDERER = Formatter.builder(markdownOptions).build()
5048

5149
def parseToMarkdown(text: String, extensions: Extension*): mdu.Document =
50+
val options =
51+
if extensions.isEmpty then
52+
markdownOptions
53+
else
54+
mkMarkdownOptions(extensions)
55+
5256
val thisParser =
53-
if(extensions.isEmpty) parser
54-
else Parser.builder(mkMarkdownOptions(extensions)).build()
57+
Parser.builder(options)
58+
.customBlockParserFactory(parsers.WikiCodeBlockParser.Factory())
59+
.build()
5560

5661
// We need to remove safe tag markers as they break flexmark parsing
5762
thisParser.parse(text.replace(safeTagMarker.toString, "")).asInstanceOf[mdu.Document]

0 commit comments

Comments
 (0)