diff --git a/scaladoc-js/resources/scaladoc-searchbar.css b/scaladoc-js/resources/scaladoc-searchbar.css
index 7c81c3fa75b6..66c847d5778a 100644
--- a/scaladoc-js/resources/scaladoc-searchbar.css
+++ b/scaladoc-js/resources/scaladoc-searchbar.css
@@ -124,34 +124,206 @@
margin-left: auto;
}
-.snippet-comment-button {
- position: absolute;
+/* Snippets */
+
+.snippet {
+ cursor: default;
+}
+
+.snippet .snippet-meta {
+ border-top: 2px solid var(--inactive-bg);
+ color: var(--inactive-fg);
+ margin-top: 10px;
+ padding-top: 10px;
+ font-size: 0.75em;
+}
+
+.snippet-meta .snippet-label {
+ font-weight: bold;
+}
+
+.snippet .buttons {
+ --icon-size: 16px;
+}
+
+.snippet-showhide {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ --slider-width: 40px;
+ --slider-height: 16px;
+ --slider-diameter: calc(var(--slider-height) - 4px);
+}
+
+.snippet-showhide p {
+ margin-left: 4px;
+ margin-bottom: 0;
+ margin-top: 0;
+ color: var(--inactive-fg);
+}
+
+.snippet-showhide-button {
display: inline-block;
- left: 50%;
- width: 24px;
- height: 24px;
- background:
- linear-gradient(#fff, #fff),
- linear-gradient(#fff, #fff),
- #aaa;
- background-position: center;
- background-size: 50% 2px, 2px 50%;
- background-repeat: no-repeat;
- border-radius: 12px;
- box-shadow: 0 0 2px var(--black);
-}
-
-.snippet-comment-button:hover {
- background:
- linear-gradient(#444, #444),
- linear-gradient(#444, #444),
- #ddd;
- background-position: center;
- background-size: 50% 2px, 2px 50%;
- background-repeat: no-repeat;
-}
-
-.hide-snippet-comments-button {
- -ms-transform: rotate(45deg);
- transform: rotate(45deg);
+ position: relative;
+ width: var(--slider-width);
+ height: var(--slider-height);
+ margin-bottom: 0;
+}
+
+.snippet-showhide-button input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.snippet-showhide-button .slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--inactive-bg);
+ -webkit-transition: .4s;
+ transition: .4s;
+ border-radius: var(--slider-height);
+}
+
+.snippet-showhide-button .slider:before {
+ position: absolute;
+ content: "";
+ height: var(--slider-diameter);
+ width: var(--slider-diameter);
+ left: 2px;
+ bottom: 2px;
+ background-color: var(--inactive-fg);
+ -webkit-transition: .4s;
+ transition: .4s;
+ border-radius: 50%;
+}
+
+.snippet-showhide-button .slider:hover::before {
+ background-color: var(--active-fg);
+}
+
+input:checked + .slider {
+ background-color: var(--active-bg);
+}
+
+input:focus + .slider {
+ box-shadow: 0 0 1px var(--active-bg-shadow);
+}
+
+input:checked + .slider:before {
+ --translation-size: calc(var(--slider-width) - var(--slider-diameter) - 4px);
+ -webkit-transform: translateX(var(--translation-size));
+ -ms-transform: translateX(var(--translation-size));
+ transform: translateX(var(--translation-size));
+}
+
+.snippet .buttons .tooltip::after {
+ top: 32px;
+}
+
+.snippet .buttons {
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: flex-start;
+}
+
+.snippet .buttons button {
+ outline: none;
+ background: none;
+ border: none;
+ font-size: var(--icon-size);
+ color: var(--inactive-fg);
+ cursor: pointer;
}
+
+.snippet .buttons button:hover:not(:disabled) {
+ color: var(--inactive-fg-shadow)
+}
+
+.snippet .buttons button:active:not(:disabled) {
+ transform: translateY(2px);
+ color: var(--active-fg)
+}
+
+.snippet .buttons button:disabled {
+ color: var(--inactive-bg)
+}
+
+
+.snippet .buttons>:not(:last-child) {
+ border-left: 2px solid var(--inactive-bg);
+}
+
+.snippet .buttons>* {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.unselectable {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.included-section {
+ display: flex;
+ flex-direction: column;
+}
+
+.included-section a {
+ color: var(--inactive-fg) !important;
+ font-size: 0.75em;
+}
+
+.included-section b {
+ font-weight: bold;
+}
+
+.hideable.hidden {
+ display: none;
+}
+
+@media(max-width: 576px) {
+ .snippet-showhide {
+ --slider-width: 32px;
+ --slider-height: 16px;
+ }
+
+ .snippet .buttons {
+ --icon-size: 16px;
+ font-size: 16px;
+ }
+
+}
+
+@media(max-width: 360px) {
+ .snippet-showhide {
+ --slider-width: 32px;
+ --slider-height: 16px;
+ }
+
+ .snippet .buttons {
+ --icon-size: 16px;
+ font-size: 0px;
+ }
+
+}
+
+@media(max-width: 240px) {
+ .snippet-showhide {
+ --slider-width: 24px;
+ --slider-height: 10px;
+ }
+
+ .snippet .buttons {
+ --icon-size: 16px;
+ font-size: 0px;
+ }
+
+}
+
diff --git a/scaladoc-js/src/code-snippets/CodeSnippets.scala b/scaladoc-js/src/code-snippets/CodeSnippets.scala
new file mode 100644
index 000000000000..248400ea49c6
--- /dev/null
+++ b/scaladoc-js/src/code-snippets/CodeSnippets.scala
@@ -0,0 +1,130 @@
+package dotty.tools.scaladoc
+
+import org.scalajs.dom._
+import org.scalajs.dom.ext._
+
+class CodeSnippets:
+
+ private def getButtonsSection(snippet: html.Element): Option[html.Div] = snippet.querySelector("div.buttons") match {
+ case div: html.Div => Some(div)
+ case _ => None
+ }
+
+ def enrichSnippets() = document.querySelectorAll("div.snippet").foreach {
+ case snippet: html.Element =>
+ snippet.addEventListener("click", e => e.stopPropagation())
+ snippetAnchor(snippet)
+ handleHideableCode(snippet)
+ handleImportedCode(snippet)
+ copyRunButtons(snippet)
+ }
+
+ private def handleHideableCode(snippet: html.Element): Unit = {
+ def toggleHide(e: html.Element | html.Document) = e.querySelectorAll(".hideable").foreach {
+ case e: html.Element => e.classList.toggle("hidden")
+ case _ =>
+ }
+ def createShowHideButton(toggleRoot: html.Element) = {
+ val div = document.createElement("div")
+ div.classList.add("snippet-showhide")
+ val p = document.createElement("p")
+ p.textContent = "Show collapsed lines"
+ val showHideButton = document.createElement("label")
+ showHideButton.classList.add("snippet-showhide-button")
+ val checkbox = document.createElement("input").asInstanceOf[html.Input]
+ checkbox.`type` = "checkbox"
+ val slider = document.createElement("span")
+ slider.classList.add("slider")
+ showHideButton.appendChild(checkbox)
+ showHideButton.appendChild(slider)
+ checkbox.addEventListener("change", _ => toggleHide(toggleRoot))
+ div.appendChild(showHideButton)
+ div.appendChild(p)
+ div
+ }
+
+ toggleHide(snippet)
+ val buttonsSection = getButtonsSection(snippet)
+ val hideables = snippet.querySelectorAll(".hideable")
+ if hideables != null && hideables.nonEmpty then {
+ val showHideButton = createShowHideButton(snippet)
+ buttonsSection.foreach(_.appendChild(showHideButton))
+ }
+ }
+
+ private def snippetAnchor(snippet: html.Element): Unit = snippet.querySelector(".snippet-meta .snippet-label") match {
+ case e: html.Element =>
+ val name = e.textContent.trim
+ val anchor = document.createElement("a").asInstanceOf[html.Anchor]
+ anchor.id = s"snippet-$name"
+ snippet.insertBefore(anchor, snippet.firstChild)
+ case _ =>
+ }
+
+ private def handleImportedCode(snippet: html.Element): Unit = {
+ val included = snippet.querySelectorAll("code span.include")
+ val pre = snippet.querySelector("pre")
+ if included != null && included.nonEmpty && pre != null then {
+ val includesDiv = document.createElement("div")
+ includesDiv.classList.add("included-section")
+ includesDiv.classList.add("hideable")
+ included
+ .collect { case e: html.Element => e }
+ .toList
+ .filter(_.hasAttribute("name"))
+ .map(_.getAttribute("name"))
+ .distinct
+ .map { name =>
+ val a = document.createElement("a").asInstanceOf[html.Anchor]
+ a.classList.add("unselectable")
+ a.href = s"#snippet-$name"
+ a.innerHTML = s"included $name"
+ a
+ }
+ .foreach(a => includesDiv.appendChild(a))
+
+ snippet.insertBefore(includesDiv, pre)
+ }
+ }
+
+ private def copyRunButtons(snippet: html.Element) = {
+ def copyButton = {
+ val div = document.createElement("div")
+ val button = document.createElement("button")
+ val icon = document.createElement("i")
+ icon.classList.add("far")
+ icon.classList.add("fa-clone")
+ button.appendChild(icon)
+ button.classList.add("copy-button")
+ button.addEventListener("click", _ => {
+ val code = snippet.querySelectorAll("code>span:not(.hidden)")
+ .map(_.textContent)
+ .mkString
+ window.navigator.clipboard.writeText(code)
+ })
+ div.appendChild(button)
+ div
+ }
+ def runButton = {
+ val div = document.createElement("div")
+ val button = document.createElement("button").asInstanceOf[html.Button]
+ val icon = document.createElement("i")
+ icon.classList.add("fas")
+ icon.classList.add("fa-play")
+ button.appendChild(icon)
+ button.classList.add("run-button")
+ button.addEventListener("click", _ => {}) // TODO: Run button #13065
+ button.disabled = true
+ div.appendChild(button)
+ div
+ }
+ val buttonsSection = getButtonsSection(snippet)
+ buttonsSection.foreach(s =>
+ s.appendChild(copyButton)
+ // Temporarily disabled
+ // s.appendChild(runButton)
+ )
+ }
+
+ enrichSnippets()
+
diff --git a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala b/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala
deleted file mode 100644
index c8121b31860d..000000000000
--- a/scaladoc-js/src/searchbar/code-snippets/CodeSnippets.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-package dotty.tools.scaladoc
-
-import org.scalajs.dom._
-import org.scalajs.dom.ext._
-
-class CodeSnippets:
- def toggleHide(e: html.Element | html.Document) = e.querySelectorAll("code span.hideable").foreach {
- case e: html.Element if e.style.getPropertyValue("display").isEmpty => e.style.setProperty("display", "none")
- case e: html.Element => e.style.removeProperty("display")
- }
-
- toggleHide(document)
-
- document.querySelectorAll("pre").foreach {
- case e: html.Element if e.querySelectorAll("code span.hideable").nonEmpty =>
- val a = document.createElement("a")
- a.addEventListener("click", { (_: MouseEvent) =>
- if(a.classList.contains("hide-snippet-comments-button")) {
- a.classList.remove("hide-snippet-comments-button")
- } else {
- a.classList.add("hide-snippet-comments-button")
- }
- toggleHide(e)
- })
- a.classList.add("snippet-comment-button")
- e.insertBefore(a, e.firstChild)
- case e => // skip
- }
diff --git a/scaladoc-testcases/src/tests/snippetTestcase1.scala b/scaladoc-testcases/src/tests/snippetTestcase1.scala
index 4a2960092c95..dccb92368041 100644
--- a/scaladoc-testcases/src/tests/snippetTestcase1.scala
+++ b/scaladoc-testcases/src/tests/snippetTestcase1.scala
@@ -2,11 +2,28 @@ package tests.snippetTestcase1
class SnippetTestcase1:
/**
- * SNIPPET(OUTERLINEOFFSET:8,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:3,INNERCOLUMNOFFSET:2)
+ * SNIPPET(OUTERLINEOFFSET:8,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:2)
* ERROR(LINE:8,COLUMN:8)
* ```scala sc:fail
* 2 + List()
* ```
*
*/
- def a = 3
\ No newline at end of file
+ def a = 3
+ /**
+ * SNIPPET(OUTERLINEOFFSET:16,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:2)
+ * ```scala sc:compile sc-name:1
+ * val xs: List[Int] = List()
+ * ```
+ *
+ * SNIPPET(OUTERLINEOFFSET:21,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:2)
+ * ```scala sc:compile sc-compile-with:1 sc-name:2
+ * val ys = xs.map(x => x * 2)
+ * ```
+ *
+ * SNIPPET(OUTERLINEOFFSET:26,OUTERCOLUMNOFFSET:6,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:2)
+ * ```scala sc:compile sc-compile-with:2
+ * xs ++ ys
+ * ```
+ */
+ def b = 3
\ No newline at end of file
diff --git a/scaladoc-testcases/src/tests/snippetTestcase2.scala b/scaladoc-testcases/src/tests/snippetTestcase2.scala
index e68a339d77e8..3f99b316592f 100644
--- a/scaladoc-testcases/src/tests/snippetTestcase2.scala
+++ b/scaladoc-testcases/src/tests/snippetTestcase2.scala
@@ -7,7 +7,7 @@ trait Quotes2[A] {
type X
object Y {
/**
- * SNIPPET(OUTERLINEOFFSET:13,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:6,INNERCOLUMNOFFSET:6)
+ * SNIPPET(OUTERLINEOFFSET:13,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:8,INNERCOLUMNOFFSET:6)
* ERROR(LINE:13,COLUMN:12)
* ```scala sc:fail
* 2 + List()
@@ -19,7 +19,7 @@ trait Quotes2[A] {
val z: zModule = ???
trait zModule {
/**
- * SNIPPET(OUTERLINEOFFSET:25,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:7,INNERCOLUMNOFFSET:6)
+ * SNIPPET(OUTERLINEOFFSET:25,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:9,INNERCOLUMNOFFSET:6)
* ERROR(LINE:25,COLUMN:12)
* ```scala sc:fail
* 2 + List()
@@ -33,7 +33,7 @@ trait Quotes2[A] {
type X
object Y {
/**
- * SNIPPET(OUTERLINEOFFSET:39,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:5,INNERCOLUMNOFFSET:6)
+ * SNIPPET(OUTERLINEOFFSET:39,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:7,INNERCOLUMNOFFSET:6)
* ERROR(LINE:39,COLUMN:12)
* ```scala sc:fail
* 2 + List()
@@ -45,7 +45,7 @@ trait Quotes2[A] {
val z: zModule = ???
trait zModule {
/**
- * SNIPPET(OUTERLINEOFFSET:51,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:6,INNERCOLUMNOFFSET:6)
+ * SNIPPET(OUTERLINEOFFSET:51,OUTERCOLUMNOFFSET:10,INNERLINEOFFSET:8,INNERCOLUMNOFFSET:6)
* ERROR(LINE:51,COLUMN:12)
* ```scala sc:fail
* 2 + List()
diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala
index ee1444effc4a..a10be62644f3 100644
--- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala
@@ -74,7 +74,7 @@ case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode])
case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext):
lazy val sourceLinks = SourceLinks.load(args.sourceLinks, args.revision)(using compilerContext)
- lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler, args.snippetCompilerDebug)(using compilerContext)
+ lazy val snippetCompilerArgs = snippets.SnippetCompilerArgs.load(args.snippetCompiler)(using compilerContext)
lazy val snippetChecker = snippets.SnippetChecker(args)(using compilerContext)
diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
index 20dc4f288d44..5314c54e5bb3 100644
--- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala
@@ -55,7 +55,6 @@ object Scaladoc:
docCanonicalBaseUrl: String = "",
documentSyntheticTypes: Boolean = false,
snippetCompiler: List[String] = Nil,
- snippetCompilerDebug: Boolean = false,
noLinkWarnings: Boolean = false,
versionsDictionaryUrl: Option[String] = None,
generateInkuire : Boolean = false,
@@ -222,7 +221,6 @@ object Scaladoc:
YdocumentSyntheticTypes.get,
snippetCompiler.get,
noLinkWarnings.get,
- snippetCompilerDebug.get,
versionsDictionaryUrl.nonDefault,
generateInkuire.get,
apiSubdirectory.get,
diff --git a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
index 390bc653f246..4641555fb9b8 100644
--- a/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala
@@ -114,9 +114,6 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
val snippetCompiler: Setting[List[String]] =
MultiStringSetting("-snippet-compiler", "snippet-compiler", snippets.SnippetCompilerArgs.usage)
- val snippetCompilerDebug: Setting[Boolean] =
- BooleanSetting("-Ysnippet-compiler-debug", snippets.SnippetCompilerArgs.debugUsage, false)
-
val generateInkuire: Setting[Boolean] =
BooleanSetting("-Ygenerate-inkuire", "Generates InkuireDB and enables Hoogle-like searches", false)
@@ -124,4 +121,4 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
BooleanSetting("-Yapi-subdirectory", "Put the API documentation pages inside a directory `api/`", false)
def scaladocSpecificSettings: Set[Setting[_]] =
- Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug, generateInkuire)
+ Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire)
diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
index fdc29d836880..04fa68fafc42 100644
--- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala
@@ -84,6 +84,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
val urls = List(
"https://code.jquery.com/jquery-3.5.1.min.js",
+ "https://use.fontawesome.com/releases/v5.15.3/js/all.js",
"https://d3js.org/d3.v6.min.js",
"https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.2/dist/graphlib-dot.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.6.1/dagre-d3.min.js",
diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala
index d14970423e43..fe17973ecda8 100644
--- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala
@@ -106,7 +106,7 @@ case class TemplateFile(
// Snippet compiler currently supports markdown only
val parser: Parser = Parser.builder(defaultMarkdownOptions).build()
val parsedMd = parser.parse(rendered)
- val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, ssctx.snippetCompilerArgs.debug, snippetCheckingFunc)(using ssctx.outerCtx)
+ val processed = FlexmarkSnippetProcessor.processSnippets(parsedMd, snippetCheckingFunc)(using ssctx.outerCtx)
HtmlRenderer.builder(defaultMarkdownOptions).build().render(processed)
layoutTemplate match
diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala
index 0b1bea5df2e9..f6ea9dcad749 100644
--- a/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/snippets/FlexmarkSnippetProcessor.scala
@@ -10,47 +10,81 @@ import collection.JavaConverters._
import dotty.tools.scaladoc.tasty.comments.markdown.ExtendedFencedCodeBlock
object FlexmarkSnippetProcessor:
- def processSnippets(root: mdu.Node, debug: Boolean, checkingFunc: => SnippetChecker.SnippetCheckingFunc)(using CompilerContext): mdu.Node = {
+ def processSnippets(root: mdu.Node, checkingFunc: => SnippetChecker.SnippetCheckingFunc)(using CompilerContext): mdu.Node = {
lazy val cf: SnippetChecker.SnippetCheckingFunc = checkingFunc
val nodes = root.getDescendants().asScala.collect {
case fcb: mda.FencedCodeBlock => fcb
}.toList
- nodes.foreach { node =>
- val snippet = node.getContentChars.toString
+ nodes.foldLeft[Map[String, String]](Map()) { (snippetMap, node) =>
val lineOffset = node.getStartLineNumber
val info = node.getInfo.toString.split(" ")
if info.contains("scala") then {
- val argOverride =
- info
- .find(_.startsWith("sc:"))
- .map(_.stripPrefix("sc:"))
- .map(SCFlagsParser.parse)
- .flatMap(_ match {
- case Right(flags) => Some(flags)
- case Left(error) =>
- report.warning(
- s"""|Error occured during parsing flags in snippet:
- |$error""".stripMargin
- )
- None
- })
- val snippetCompilationResult = cf(snippet, lineOffset, argOverride) match {
- case result@Some(SnippetCompilationResult(wrapped, _, _, _)) if debug =>
+ val argOverride = info
+ .find(_.startsWith("sc:"))
+ .map(_.stripPrefix("sc:"))
+ .map(SCFlagsParser.parse)
+ .flatMap(_ match {
+ case Right(flags) => Some(flags)
+ case Left(error) =>
+ report.warning(
+ s"""|Error occured during parsing flags in snippet:
+ |$error""".stripMargin
+ )
+ None
+ })
+ val id = info
+ .find(_.startsWith("sc-name:"))
+ .map(_.stripPrefix("sc-name:"))
+
+ val snippetImports = info
+ .find(_.startsWith("sc-compile-with:"))
+ .toList
+ .map(_.stripPrefix("sc-compile-with:"))
+ .flatMap(_.split(","))
+ .flatMap { id =>
+ val snippet = snippetMap.get(id)
+ if snippet.isEmpty then
+ report.warning(
+ s"""|Error occured during parsing compile-with in snippet:
+ |Snippet with id: $id not found.
+ |Remember that you cannot use forward reference to snippets""".stripMargin
+ )
+ snippet
+ }.mkString("\n")
+
+ val snippet = node.getContentChars.toString
+
+ extension (n: mdu.Node)
+ def setContentString(str: String): Unit =
val s = sequence.BasedSequence.EmptyBasedSequence()
- .append(wrapped.snippet)
+ .append(str)
.append(sequence.BasedSequence.EOL)
val content = mdu.BlockContent()
content.add(s, 0)
node.setContent(content)
+
+ val fullSnippet = Seq(snippetImports, snippet).mkString("\n").trim
+ val snippetCompilationResult = cf(fullSnippet, lineOffset, argOverride) match {
+ case result@Some(SnippetCompilationResult(wrapped, _, _, _)) =>
+ node.setContentString(wrapped.snippet)
+ result
+ case result =>
+ node.setContentString(fullSnippet)
result
- case result => result
}
- node.insertBefore(ExtendedFencedCodeBlock(node, snippetCompilationResult))
+ node.insertBefore(ExtendedFencedCodeBlock(id, node, snippetCompilationResult))
node.unlink()
- }
+ id.fold(snippetMap)(id =>
+ val snippetAsImport = s"""|//{i:$id
+ |$snippet
+ |//i}""".stripMargin
+ val entry = (id, Seq(snippetImports, snippetAsImport).mkString("\n"))
+ snippetMap + entry
+ )
+ } else snippetMap
}
root
diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala
index 9fdcfc8e46cf..00f89b94c626 100644
--- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompiler.scala
@@ -59,7 +59,7 @@ class SnippetCompiler(
case diagnostic if diagnostic.position.isPresent =>
val diagPos = diagnostic.position.get
val pos = Some(
- Position(diagPos.line + line - innerLineOffset, diagPos.column + column - innerColumnOffset, diagPos.lineContent, if arg.debug then diagPos.line else diagPos.line - innerLineOffset)
+ Position(diagPos.line + line - innerLineOffset, diagPos.column + column - innerColumnOffset, diagPos.lineContent, diagPos.line)
)
val dmsg = Try(diagnostic.message) match {
case Success(msg) => msg
diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala
index dcba74b12533..72bbd291b6da 100644
--- a/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/snippets/SnippetCompilerArgs.scala
@@ -3,7 +3,7 @@ package snippets
import java.nio.file.Path
-case class SnippetCompilerArg(flag: SCFlags, debug: Boolean):
+case class SnippetCompilerArg(flag: SCFlags):
def overrideFlag(f: SCFlags): SnippetCompilerArg = copy(flag = f)
sealed trait SCFlags(val flagName: String)
@@ -15,16 +15,16 @@ object SCFlags:
def values: Seq[SCFlags] = Seq(Compile, NoCompile, Fail)
-case class SnippetCompilerArgs(scFlags: PathBased[SCFlags], val debug: Boolean, defaultFlag: SCFlags):
+case class SnippetCompilerArgs(scFlags: PathBased[SCFlags], defaultFlag: SCFlags):
def get(member: Member): SnippetCompilerArg =
member.sources
.flatMap(s => scFlags.get(s.path).map(_.elem))
- .fold(SnippetCompilerArg(defaultFlag, debug))(SnippetCompilerArg(_, debug))
+ .fold(SnippetCompilerArg(defaultFlag))(SnippetCompilerArg(_))
def get(path: Option[Path]): SnippetCompilerArg =
path
.flatMap(p => scFlags.get(p).map(_.elem))
- .fold(SnippetCompilerArg(defaultFlag, debug))(SnippetCompilerArg(_, debug))
+ .fold(SnippetCompilerArg(defaultFlag))(SnippetCompilerArg(_))
object SnippetCompilerArgs:
@@ -46,11 +46,7 @@ object SnippetCompilerArgs:
|
""".stripMargin
- val debugUsage = """
- |Setting this option causes snippet compiler to print snippet as it is compiled (after wrapping).
- """.stripMargin
-
- def load(args: List[String], debug: Boolean, defaultFlag: SCFlags = SCFlags.NoCompile)(using CompilerContext): SnippetCompilerArgs = {
+ def load(args: List[String], defaultFlag: SCFlags = SCFlags.NoCompile)(using CompilerContext): SnippetCompilerArgs = {
PathBased.parse[SCFlags](args)(using SCFlagsParser) match {
case PathBased.ParsingResult(errors, res) =>
if errors.nonEmpty then report.warning(s"""
@@ -60,7 +56,7 @@ object SnippetCompilerArgs:
|$usage
|""".stripMargin
)
- SnippetCompilerArgs(res, debug, defaultFlag)
+ SnippetCompilerArgs(res, defaultFlag)
}
}
diff --git a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala
index e12078217c7e..d3ad8db8f9d3 100644
--- a/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/snippets/WrappedSnippet.scala
@@ -13,11 +13,15 @@ object WrappedSnippet:
def apply(str: String): WrappedSnippet =
val baos = new ByteArrayOutputStream()
val ps = new PrintStream(baos)
+ ps.startHide()
ps.println("package snippets")
ps.println("object Snippet {")
+ ps.endHide()
str.split('\n').foreach(ps.printlnWithIndent(indent, _))
+ ps.startHide()
ps.println("}")
- WrappedSnippet(baos.toString, 0, 0, indent, indent)
+ ps.endHide()
+ WrappedSnippet(baos.toString, 0, 0, indent + 2 /*Hide tokens*/, indent)
def apply(
str: String,
@@ -29,6 +33,7 @@ object WrappedSnippet:
): WrappedSnippet =
val baos = new ByteArrayOutputStream()
val ps = new PrintStream(baos)
+ ps.startHide()
ps.println(s"package ${packageName.getOrElse("snippets")}")
imports.foreach(i => ps.println(s"import $i"))
val notEmptyClassInfos = if classInfos.isEmpty then Seq(SnippetCompilerData.ClassInfo(None, Nil, None)) else classInfos
@@ -38,17 +43,23 @@ object WrappedSnippet:
ps.printlnWithIndent(indent * i + indent, s"val $name = self")
}
}
+ ps.endHide()
str.split('\n').foreach(ps.printlnWithIndent(notEmptyClassInfos.size * indent, _))
+ ps.startHide()
(0 to notEmptyClassInfos.size -1).reverse.foreach( i => ps.printlnWithIndent(i * indent, "}"))
+ ps.endHide()
WrappedSnippet(
baos.toString,
outerLineOffset,
outerColumnOffset,
- notEmptyClassInfos.size + notEmptyClassInfos.flatMap(_.names).size + packageName.size,
+ notEmptyClassInfos.size + notEmptyClassInfos.flatMap(_.names).size + packageName.size + 2 /*Hide tokens*/,
notEmptyClassInfos.size * indent
)
- extension (ps: PrintStream) private def printlnWithIndent(indent: Int, str: String) =
- ps.println((" " * indent) + str)
+ extension (ps: PrintStream)
+ private def printlnWithIndent(indent: Int, str: String) =
+ ps.println((" " * indent) + str)
+ private def startHide() = ps.println(raw"//{")
+ private def endHide() = ps.println(raw"//}")
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala
index 345b2399abe8..bcd7dfcb438d 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala
@@ -197,7 +197,7 @@ class MarkdownCommentParser(repr: Repr)(using dctx: DocContext)
.mapValues(stringToMarkup).to(SortedMap)
def processSnippets(root: mdu.Node): mdu.Node =
- FlexmarkSnippetProcessor.processSnippets(root, dctx.snippetCompilerArgs.debug, snippetCheckingFunc(owner))
+ FlexmarkSnippetProcessor.processSnippets(root, snippetCheckingFunc(owner))
}
class WikiCommentParser(repr: Repr)(using DocContext)
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala
index 2868b901f191..14ea61b5c9da 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/DocFlexmarkExtension.scala
@@ -22,9 +22,10 @@ class DocLinkNode(
) extends WikiNode(seq, false, false, false, false)
case class ExtendedFencedCodeBlock(
+ name: Option[String],
codeBlock: ast.FencedCodeBlock,
compilationResult: Option[SnippetCompilationResult]
-) extends WikiNode(codeBlock.getChars, false, false, false, false)
+) extends BlankLine(codeBlock.getContentChars())
class DocFlexmarkParser(resolveLink: String => DocLink) extends Parser.ParserExtension:
diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala
index 76403af24f7c..dd87527625d5 100644
--- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala
+++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/markdown/SnippetRenderer.scala
@@ -1,17 +1,28 @@
-package dotty.tools.scaladoc.tasty.comments.markdown
+package dotty.tools.scaladoc
+package tasty.comments.markdown
import com.vladsch.flexmark.html._
+import util.HTML._
import dotty.tools.scaladoc.snippets._
import dotty.tools.scaladoc.util.HTML._
-case class SnippetLine(content: String, lineNo: Int, classes: Set[String] = Set.empty, messages: Seq[String] = Seq.empty):
+case class SnippetLine(content: String, lineNo: Int, classes: Set[String] = Set.empty, messages: Seq[String] = Seq.empty, attributes: Map[String, String] = Map.empty):
def withClass(cls: String) = this.copy(classes = classes + cls)
+ def withAttribute(name: String, value: String) = this.copy(attributes = attributes.updated(name, value))
+ private def attributesToString: String = attributes.updated("id", lineNo).map((key, value) => s"""$key="$value"""").mkString(" ")
def toHTML =
val label = if messages.nonEmpty then s"""label="${messages.map(_.escapeReservedTokens).mkString("\n")}"""" else ""
- s"""$content"""
+ s"""$content"""
object SnippetRenderer:
+ val hiddenStartSymbol = "//{"
+ val hiddenEndSymbol = "//}"
+
+ val importedStartSymbol = "//{i"
+ val importedEndSymbol = "//i}"
+ val importedRegex = """\/\/\{i:(.*)""".r
+
private def compileMessageCSSClass(msg: SnippetCompilerMessage) = msg.level match
case MessageLevel.Info => "snippet-info"
case MessageLevel.Warning => "snippet-warn"
@@ -32,13 +43,33 @@ object SnippetRenderer:
(begin, mid) = tmp.splitAt(startIdx)
} yield f(begin, mid, end)
+ private def wrapImportedSection(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] =
+ val mRes = cutBetweenSymbols(importedStartSymbol, importedEndSymbol, snippetLines) {
+ case (begin, mid, end) =>
+ val name = importedRegex.findFirstMatchIn(mid.head.content).fold("")(_.group(1))
+ begin ++ mid.drop(1).dropRight(1).map(_.withClass("hideable").withClass("include").withAttribute("name", name)) ++ wrapImportedSection(end)
+ }
+ mRes.getOrElse(snippetLines)
+
private def wrapHiddenSymbols(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] =
- val mRes = cutBetweenSymbols("//{", "//}", snippetLines) {
+ val mRes = cutBetweenSymbols(hiddenStartSymbol, hiddenEndSymbol, snippetLines) {
case (begin, mid, end) =>
begin ++ mid.drop(1).dropRight(1).map(_.withClass("hideable")) ++ wrapHiddenSymbols(end)
}
mRes.getOrElse(snippetLines)
+ private def wrapCommonIndent(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] =
+ val nonHiddenSnippetLines = snippetLines.filter(l => !l.classes.contains("hideable"))
+ nonHiddenSnippetLines.headOption.map(_.content.takeWhile(_ == ' ')).map { prefix =>
+ val maxCommonIndent = nonHiddenSnippetLines.foldLeft(prefix) { (currPrefix, elem) =>
+ if elem.content.startsWith(currPrefix) then currPrefix else elem.content.takeWhile(_ == ' ')
+ }
+ snippetLines.map { line =>
+ if line.classes.contains("hideable") then line
+ else line.copy(content = span(cls := "hideable")(maxCommonIndent).toString + line.content.stripPrefix(maxCommonIndent))
+ }
+ }.getOrElse(snippetLines)
+
private def wrapLineInBetween(startSymbol: Option[String], endSymbol: Option[String], line: SnippetLine): SnippetLine =
val startIdx = startSymbol.map(s => line.content.indexOf(s))
val endIdx = endSymbol.map(s => line.content.indexOf(s))
@@ -65,35 +96,13 @@ object SnippetRenderer:
line.copy(content = begin + s"""$comment""" + end)
case _ => line
- private def wrapSingleLineComments(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] =
- snippetLines.map { line =>
- line.content.indexOf("//") match
- case -1 => line
- case idx =>
- wrapLineInBetween(Some("//"), None, line)
- }
-
- private def wrapMultiLineComments(snippetLines: Seq[SnippetLine]): Seq[SnippetLine] =
- val mRes = cutBetweenSymbols("/*", "*/", snippetLines) {
- case (begin, mid, end) if mid.size == 1 =>
- val midRedacted = mid.map(wrapLineInBetween(Some("/*"), Some("*/"), _))
- begin ++ midRedacted ++ end
- case (begin, mid, end) =>
- val midRedacted =
- mid.take(1).map(wrapLineInBetween(Some("/*"), None, _))
- ++ mid.drop(1).dropRight(1).map(_.withClass("hideable"))
- ++ mid.takeRight(1).map(wrapLineInBetween(None, Some("*/"), _))
- begin ++ midRedacted ++ wrapMultiLineComments(end)
- }
- mRes.getOrElse(snippetLines)
-
private def wrapCodeLines(codeLines: Seq[String]): Seq[SnippetLine] =
val snippetLines = codeLines.zipWithIndex.map {
case (content, idx) => SnippetLine(content.escapeReservedTokens, idx)
}
- wrapHiddenSymbols
- .andThen(wrapSingleLineComments)
- .andThen(wrapMultiLineComments)
+ wrapImportedSection
+ .andThen(wrapHiddenSymbols)
+ .andThen(wrapCommonIndent)
.apply(snippetLines)
private def addCompileMessages(messages: Seq[SnippetCompilerMessage])(codeLines: Seq[SnippetLine]): Seq[SnippetLine] =
@@ -121,7 +130,11 @@ object SnippetRenderer:
.mkString("
")
s"""
${transformedLines.mkString("")}
"""
- s"""$codeHTML
${transformedLines.mkString("")}
"""
+ s"""$codeHTML${snippetName.fold("")(snippetLabel(_))}