package dotty.tools.dottydoc
package js
package html
import scalatags.JsDom.all._
import scalatags.JsDom.TypedTag
import org.scalajs.dom
import org.scalajs.dom.html.{Anchor, Div, Span}
trait MemberLayout {
import js.model._
def member(m: Entity, parent: Entity) = {
def toggleBetween(short: Div, and: Div): Unit =
if (and.style.display == "none") {
and.style.display = "block"
short.style.display = "none"
} else {
and.style.display = "none"
short.style.display = "block"
}
m.kind match {
case "class" | "case class" | "object" | "trait" | "def" | "val" =>
val entity = m.asInstanceOf[Entity with Modifiers]
val shortComment = div(
cls := "mdl-cell mdl-cell--12-col summary-comment",
raw(m.comment.fold("")(_.short))
).render
val fullComment = div(
cls := "mdl-cell mdl-cell--12-col full-comment",
style := "display: none;",
raw(m.comment.fold("")(_.body))
).render
val hasLongerFullComment = m.comment.fold(false) { c =>
c.short.length + 5 < c.body.length
}
val divs = div(
cls :=
s"""
mdl-cell mdl-cell--12-col member
${if (hasLongerFullComment) "member-fullcomment" else ""}
""",
onclick := { () => toggleBetween(shortComment, and = fullComment) },
div(
cls := "mdl-cell mdl-cell--12-col member-definition",
span(
cls := "member-modifiers-kind",
entity.modifiers.mkString(" ") + " " + m.kind
),
span(
cls := "member-name",
m.name
),
spanWith("member-type-params no-left-margin", typeParams(m)),
span(cls := "member-param-list no-left-margin", paramList(m)),
returnValue(entity, parent)
),
shortComment,
fullComment
)
Seq(divs)
case _ => Seq(h1("ERROR: " + m.name))
}
}
def spanWith(clazz: String, contents: String) = contents match {
case "" => None
case _ => Some(span(cls := clazz, contents))
}
def paramList(m: Entity): Span = m.kind match {
case "def" =>
val d = m.asInstanceOf[Def]
if (d.paramLists.nonEmpty)
span(
cls := "member-param-lists",
d.paramLists.map { xs =>
span(
cls := "param-list",
"(",
span(cls := "is-implicit no-left-margin", if (xs.isImplicit) "implicit " else ""),
xs.list.flatMap { tr =>
Seq(
span(cls := "param-name", tr.title).render,
span(cls := "type-separator no-left-margin", if (tr.isByName) ": =>" else ":").render,
span(if (tr.ref.kind == "FunctionReference" && tr.isRepeated) "(" else "").render,
span(referenceToLinks(tr.ref)).render,
span(if (tr.ref.kind == "FunctionReference" && tr.isRepeated) ")*" else if (tr.isRepeated) "*" else "").render,
span(cls := "type-separator no-left-margin", ",").render
)
}.toList.dropRight(1),
")"
).render
}.toList
).render
else span().render
case _ => span().render
}
def referenceToLinks(ref: Reference): Span = {
def linkToAnchor(link: MaterializableLink) = link.kind match {
case "MaterializedLink" =>
val (t, url) = (link.asInstanceOf[MaterializedLink].title, link.asInstanceOf[MaterializedLink].target)
a(href := url, t).render
case "NoLink" => span(link.title).render
case "UnsetLink" =>
println(s"UnsetLink found: $link")
span(link.title).render
}
ref.kind match {
case "TypeReference" =>
val tref = ref.asInstanceOf[TypeReference]
val infixTypes = "<:<" :: "=:=" :: Nil
if (tref.paramLinks.length == 2 && infixTypes.contains(tref.title)) span(
referenceToLinks(tref.paramLinks(0)),
span(cls := "type-separator no-left-margin"),
linkToAnchor(tref.tpeLink),
span(cls := "type-separator no-left-margin"),
referenceToLinks(tref.paramLinks(1))
).render
else if (tref.paramLinks.nonEmpty) span(
linkToAnchor(tref.tpeLink),
"[",
tref
.paramLinks
.map(referenceToLinks)
.flatMap(link => Seq(link, span(cls := "type-separator no-left-margin", ",").render))
.toList.dropRight(1),
"]"
).render
else span(linkToAnchor(tref.tpeLink)).render
case "OrTypeReference" =>
val (left, right) = (ref.asInstanceOf[OrTypeReference].left, ref.asInstanceOf[OrTypeReference].right)
span(
referenceToLinks(left),
span(cls := "type-separator", "|"),
referenceToLinks(right)
).render
case "AndTypeReference" =>
val (left, right) = (ref.asInstanceOf[AndTypeReference].left, ref.asInstanceOf[AndTypeReference].right)
span(
referenceToLinks(left),
span(cls := "type-separator", "&"),
referenceToLinks(right)
).render
case "BoundsReference" =>
val (low, high) = (ref.asInstanceOf[BoundsReference].low, ref.asInstanceOf[BoundsReference].high)
span(
referenceToLinks(low),
span(cls := "type-separator", "<:"),
referenceToLinks(high)
).render
case "FunctionReference" => {
val func = ref.asInstanceOf[FunctionReference]
span(
cls := "no-left-margin",
if (func.args.length > 1) "(" else "",
if (func.args.isEmpty)
span("()")
else func
.args
.map(referenceToLinks)
.flatMap(link => Seq(link, span(cls := "type-separator no-left-margin", ",").render)).init.toList,
if (func.args.length > 1) ") => " else " => ",
referenceToLinks(func.returnValue)
).render
}
case "TupleReference" => {
val func = ref.asInstanceOf[TupleReference]
span(
cls := "no-left-margin",
"(",
func
.args
.map(referenceToLinks)
.flatMap(link => Seq(link, span(cls := "type-separator no-left-margin", ",").render)).init.toList,
")"
).render
}
}
}
def typeParams(m: Entity): String = m.kind match {
case "def" =>
val d = m.asInstanceOf[Def]
if (d.typeParams.nonEmpty)
d.typeParams.mkString("[", ", ", "]")
else ""
case _ => ""
}
def returnValue(m: Entity with Modifiers, parent: Entity) = {
// shortens: "Option.this.A" => "A"
def shorten(s: String): String = s.split('.').toList match {
case x :: Nil => x
case x :: xs if x == parent.name => xs.last
case xs => s
}
def link(rv: Reference): Span = {
def decodeTpeLink(link: MaterializableLink): Span = link.kind match {
case "MaterializedLink" =>
val ml = link.asInstanceOf[MaterializedLink]
span(cls := "member-return-value", a(href := ml.target, ml.title)).render
case "UnsetLink" =>
val un = link.asInstanceOf[UnsetLink]
span(cls := "member-return-value", shorten(un.query)).render
case "NoLink" =>
val no = link.asInstanceOf[NoLink]
span(cls := "member-return-value", shorten(no.title)).render
}
rv.kind match {
case "TypeReference" =>
val trv = rv.asInstanceOf[TypeReference]
val returnValue = decodeTpeLink(trv.tpeLink)
if (trv.paramLinks.nonEmpty) span(
returnValue,
"[",
trv.paramLinks
.map(link)
.flatMap { sp =>
Seq(sp, span(cls := "type-separator no-left-margin", ",").render)
}
.toList.dropRight(1),
"]"
).render
else returnValue
case "OrTypeReference" =>
val (left, right) = (rv.asInstanceOf[OrTypeReference].left, rv.asInstanceOf[OrTypeReference].right)
span(
cls := "member-return-value or-type",
link(left),
span(cls := "type-separator", "|"),
link(right)
).render
case "AndTypeReference" =>
val (left, right) = (rv.asInstanceOf[AndTypeReference].left, rv.asInstanceOf[AndTypeReference].right)
span(
cls := "member-return-value and-type",
link(left),
span(cls := "type-separator", "&"),
link(right)
).render
case "BoundsReference" =>
val (low, high) = (rv.asInstanceOf[BoundsReference].low, rv.asInstanceOf[BoundsReference].high)
span(
link(low),
span(cls := "type-separator", "<:"),
link(high)
).render
case "FunctionReference" =>
val func = rv.asInstanceOf[FunctionReference]
span(
cls := "no-left-margin",
if (func.args.length > 1) "(" else "",
if (func.args.isEmpty)
span("()")
else func
.args
.map(link)
.flatMap(link => Seq(link, span(cls := "type-separator no-left-margin", ",").render)).init.toList,
if (func.args.length > 1) ") => " else " => ",
link(func.returnValue)
).render
case "TupleReference" => {
val func = rv.asInstanceOf[TupleReference]
span(
cls := "no-left-margin",
"(",
func
.args
.map(link)
.flatMap(link => Seq(link, span(cls := "type-separator no-left-margin", ",").render)).init.toList,
")"
).render
}
}
}
m.kind match {
case "def" =>
val rv = m.asInstanceOf[ReturnValue]
Some(span(cls := "no-left-margin", ": ", link(rv.returnValue)))
case _ => None
}
}
}