summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcin Kubala <marcin@mkubala.pl>2014-03-10 23:55:16 +0100
committerMarcin Kubala <marcin@mkubala.pl>2014-03-14 22:58:07 +0100
commit466340cf9e3fbc6fbd3c65e13b43d7fe57471d86 (patch)
treedeb1d87f2a9711f3dea1d15494be52a5fc5e1693
parent8d7f73316570a4325111da3b4c0529793ced8b97 (diff)
downloadscala-466340cf9e3fbc6fbd3c65e13b43d7fe57471d86.tar.gz
scala-466340cf9e3fbc6fbd3c65e13b43d7fe57471d86.tar.bz2
scala-466340cf9e3fbc6fbd3c65e13b43d7fe57471d86.zip
SI-8144 permalinks in scaladoc
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala4
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala25
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala9
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js54
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/resource/lib/permalink.pngbin0 -> 943 bytes
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css31
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.js94
-rw-r--r--test/scaladoc/resources/SI-8144.scala17
-rw-r--r--test/scaladoc/scalacheck/HtmlFactoryTest.scala66
9 files changed, 247 insertions, 53 deletions
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala
index d721a96ad7..a0dd154d2e 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/html/HtmlFactory.scala
@@ -97,7 +97,9 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) {
"selected2.png",
"selected-right-implicits.png",
"selected-implicits.png",
- "unselected.png"
+ "unselected.png",
+
+ "permalink.png"
)
/** Generates the Scaladoc site for a model into the site root.
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala
index f6373e9e97..295bae5bef 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/html/HtmlPage.scala
@@ -14,6 +14,7 @@ import base.comment._
import model._
import scala.xml.NodeSeq
+import scala.xml.Elem
import scala.xml.dtd.{DocType, PublicID}
import scala.collection._
import java.io.Writer
@@ -219,4 +220,28 @@ abstract class HtmlPage extends Page { thisPage =>
else if (ety.isObject) "object_big.png"
else if (ety.isPackage) "package_big.png"
else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not
+
+ def permalink(template: Entity, isSelf: Boolean = true): Elem =
+ <span class="permalink">
+ <a href={ memberToUrl(template, isSelf) } title="Permalink" target="_top">
+ <img src={ relativeLinkTo(List("permalink.png", "lib")) } />
+ </a>
+ </span>
+
+ def memberToUrl(template: Entity, isSelf: Boolean = true): String = {
+ val (signature: Option[String], containingTemplate: TemplateEntity) = template match {
+ case dte: DocTemplateEntity if (!isSelf) => (Some(dte.signature), dte.inTemplate)
+ case dte: DocTemplateEntity => (None, dte)
+ case me: MemberEntity => (Some(me.signature), me.inTemplate)
+ case tpl => (None, tpl)
+ }
+
+ def hashFromPath(templatePath: List[String]): String =
+ ((templatePath.head.replace(".html", "") :: templatePath.tail).reverse).mkString(".")
+
+ val containingTemplatePath = templateToPath(containingTemplate)
+ val url = "../" * (containingTemplatePath.size - 1) + "index.html"
+ val hash = hashFromPath(containingTemplatePath)
+ s"$url#$hash" + signature.map("@" + _).getOrElse("")
+ }
}
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala b/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala
index 26ee005d3e..51fc643429 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/html/page/Template.scala
@@ -15,7 +15,7 @@ import base.comment._
import model._
import model.diagram._
-import scala.xml.{ NodeSeq, Text, UnprefixedAttribute }
+import scala.xml.{Elem, NodeSeq, Text, UnprefixedAttribute}
import scala.language.postfixOps
import scala.collection.mutable. { Set, HashSet }
@@ -110,7 +110,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
<img src={ relativeLinkTo(List(docEntityKindToBigImage(tpl), "lib")) }/>
}}
{ owner }
- <h1>{ displayName }</h1>
+ <h1>{ displayName }</h1> { permalink(tpl) }
</div>
{ signature(tpl, isSelf = true) }
@@ -723,6 +723,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
/** name, tparams, params, result */
def signature(mbr: MemberEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = {
+
def inside(hasLinks: Boolean, nameLink: String = ""): NodeSeq =
<xml:group>
<span class="modifier_kind">
@@ -833,11 +834,11 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
</xml:group>
mbr match {
case dte: DocTemplateEntity if !isSelf =>
- <h4 class="signature">{ inside(hasLinks = true, nameLink = relativeLinkTo(dte)) }</h4>
+ <h4 class="signature">{ inside(hasLinks = true, nameLink = relativeLinkTo(dte)) }</h4> ++ permalink(dte, isSelf)
case _ if isSelf =>
<h4 id="signature" class="signature">{ inside(hasLinks = true) }</h4>
case _ =>
- <h4 class="signature">{ inside(hasLinks = true) }</h4>
+ <h4 class="signature">{ inside(hasLinks = true) }</h4> ++ permalink(mbr)
}
}
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js
index c201b324e7..3f5cfb4b52 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js
+++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/index.js
@@ -1,5 +1,5 @@
// © 2009–2010 EPFL/LAMP
-// code by Gilles Dubochet with contributions by Johannes Rudolph and "spiros"
+// code by Gilles Dubochet with contributions by Johannes Rudolph, "spiros" and Marcin Kubala
var topLevelTemplates = undefined;
var topLevelPackages = undefined;
@@ -11,7 +11,7 @@ var focusFilterState = undefined;
var title = $(document).attr('title');
-var lastHash = "";
+var lastFragment = "";
$(document).ready(function() {
$('body').layout({
@@ -24,9 +24,13 @@ $(document).ready(function() {
,north__paneSelector: ".ui-west-north"
});
$('iframe').bind("load", function(){
- var subtitle = $(this).contents().find('title').text();
- $(document).attr('title', (title ? title + " - " : "") + subtitle);
-
+ try {
+ var subtitle = $(this).contents().find('title').text();
+ $(document).attr('title', (title ? title + " - " : "") + subtitle);
+ } catch (e) {
+ // Chrome doesn't allow reading the iframe's contents when
+ // used on the local file system.
+ }
setUrlFragmentFromFrameSrc();
});
@@ -64,21 +68,43 @@ $(document).ready(function() {
// Set the iframe's src according to the fragment of the current url.
// fragment = "#scala.Either" => iframe url = "scala/Either.html"
// fragment = "#scala.Either@isRight:Boolean" => iframe url = "scala/Either.html#isRight:Boolean"
+// fragment = "#scalaz.iteratee.package@>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]" => iframe url = "scalaz/iteratee/package.html#>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]"
function setFrameSrcFromUrlFragment() {
- var fragment = location.hash.slice(1);
- if(fragment) {
- var loc = fragment.split("@")[0].replace(/\./g, "/");
- if(loc.indexOf(".html") < 0) loc += ".html";
- if(fragment.indexOf('@') > 0) loc += ("#" + fragment.split("@", 2)[1]);
- frames["template"].location.replace(loc);
- }
- else
- frames["template"].location.replace("package.html");
+
+ function extractLoc(fragment) {
+ var loc = fragment.split('@')[0].replace(/\./g, "/");
+ if (loc.indexOf(".html") < 0) {
+ loc += ".html";
+ }
+ return loc;
+ }
+
+ function extractMemberSig(fragment) {
+ var splitIdx = fragment.indexOf('@');
+ if (splitIdx < 0) {
+ return;
+ }
+ return fragment.substr(splitIdx + 1);
+ }
+
+ var fragment = location.hash.slice(1);
+ if (fragment) {
+ var locWithMemeberSig = extractLoc(fragment);
+ var memberSig = extractMemberSig(fragment);
+ if (memberSig) {
+ locWithMemeberSig += "#" + memberSig;
+ }
+ frames["template"].location.replace(locWithMemeberSig);
+ } else {
+ console.log("empty fragment detected");
+ frames["template"].location.replace("package.html");
+ }
}
// Set the url fragment according to the src of the iframe "template".
// iframe url = "scala/Either.html" => url fragment = "#scala.Either"
// iframe url = "scala/Either.html#isRight:Boolean" => url fragment = "#scala.Either@isRight:Boolean"
+// iframe url = "scalaz/iteratee/package.html#>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]" => fragment = "#scalaz.iteratee.package@>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]"
function setUrlFragmentFromFrameSrc() {
try {
var commonLength = location.pathname.lastIndexOf("/");
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/permalink.png b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/permalink.png
new file mode 100644
index 0000000000..d54bc93f6a
--- /dev/null
+++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/permalink.png
Binary files differ
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css
index b066027f04..35f66cd5df 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css
+++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.css
@@ -397,6 +397,37 @@ div.members > ol > li:last-child {
margin-bottom: 5px;
}
+#template .members li .permalink {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+}
+
+#definition .permalink {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+}
+
+#definition .permalink a {
+ color: #EBEBEB;
+}
+
+#template .members li .permalink,
+#definition .permalink a {
+ display: none;
+}
+
+#template .members li:hover .permalink,
+#definition:hover .permalink a {
+ display: block;
+}
+
+#template .members li .permalink a,
+#definition .permalink a {
+ text-decoration: none;
+ font-weight: bold;
+}
/* Comments text formating */
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.js b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.js
index 6d1caf6d50..1ebcb67f04 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.js
+++ b/src/scaladoc/scala/tools/nsc/doc/html/resource/lib/template.js
@@ -1,23 +1,57 @@
// © 2009–2010 EPFL/LAMP
-// code by Gilles Dubochet with contributions by Pedro Furlanetto
+// code by Gilles Dubochet with contributions by Pedro Furlanetto and Marcin Kubala
$(document).ready(function(){
+ var controls = {
+ visibility: {
+ publicOnly: $("#visbl").find("> ol > li.public"),
+ all: $("#visbl").find("> ol > li.all")
+ }
+ };
+
// Escapes special characters and returns a valid jQuery selector
function escapeJquery(str){
- return str.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
+ return str.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=<>\|])/g, '\\$1');
}
- // highlight and jump to selected member
- if (window.location.hash) {
- var temp = window.location.hash.replace('#', '');
- var elem = '#'+escapeJquery(temp);
+ function toggleVisibilityFilter(ctrlToEnable, ctrToDisable) {
+ if (ctrlToEnable.hasClass("out")) {
+ ctrlToEnable.removeClass("out").addClass("in");
+ ctrToDisable.removeClass("in").addClass("out");
+ filter();
+ }
+ }
+
+ controls.visibility.publicOnly.click(function () {
+ toggleVisibilityFilter(controls.visibility.publicOnly, controls.visibility.all);
+ });
- window.scrollTo(0, 0);
- $(elem).parent().effect("highlight", {color: "#FFCC85"}, 3000);
- $('html,body').animate({scrollTop:$(elem).parent().offset().top}, 1000);
+ controls.visibility.all.click(function () {
+ toggleVisibilityFilter(controls.visibility.all, controls.visibility.publicOnly);
+ });
+
+ function exposeMember(jqElem) {
+ var jqElemParent = jqElem.parent(),
+ parentName = jqElemParent.attr("name"),
+ linearizationName = /^([^#]*)(#.*)?$/gi.exec(parentName)[1];
+
+ // switch visibility filter if necessary
+ if (jqElemParent.attr("visbl") == "prt") {
+ toggleVisibilityFilter(controls.visibility.all, controls.visibility.publicOnly);
+ }
+
+ // toggle appropriate linearization buttons
+ if (linearizationName) {
+ $("#linearization li.out[name='" + linearizationName + "']").removeClass("out").addClass("in");
+ }
+
+ filter();
+ window.scrollTo(0, 0);
+ jqElemParent.effect("highlight", {color: "#FFCC85"}, 3000);
+ $('html,body').animate({scrollTop: jqElemParent.offset().top}, 1000);
}
-
+
var isHiddenClass = function (name) {
return name == 'scala.Any' ||
name == 'scala.AnyRef';
@@ -97,7 +131,7 @@ $(document).ready(function(){
else if ($(this).hasClass("out")) {
$(this).removeClass("out");
$(this).addClass("in");
- };
+ }
filter();
});
@@ -109,7 +143,7 @@ $(document).ready(function(){
else if ($(this).hasClass("out")) {
$(this).removeClass("out");
$(this).addClass("in");
- };
+ }
filter();
});
@@ -147,32 +181,18 @@ $(document).ready(function(){
});
$("#visbl > ol > li.public").click(function() {
if ($(this).hasClass("out")) {
- $(this).removeClass("out").addClass("in");
- $("#visbl > ol > li.all").removeClass("in").addClass("out");
- filter();
- };
- })
- $("#visbl > ol > li.all").click(function() {
- if ($(this).hasClass("out")) {
- $(this).removeClass("out").addClass("in");
- $("#visbl > ol > li.public").removeClass("in").addClass("out");
- filter();
- };
- });
- $("#order > ol > li.alpha").click(function() {
- if ($(this).hasClass("out")) {
orderAlpha();
- };
+ }
})
$("#order > ol > li.inherit").click(function() {
if ($(this).hasClass("out")) {
orderInherit();
- };
+ }
});
$("#order > ol > li.group").click(function() {
if ($(this).hasClass("out")) {
orderGroup();
- };
+ }
});
$("#groupedMembers").hide();
@@ -181,7 +201,7 @@ $(document).ready(function(){
// Create tooltips
$(".extype").add(".defval").tooltip({
tip: "#tooltip",
- position:"top center",
+ position: "top center",
predelay: 500,
onBeforeShow: function(ev) {
$(this.getTip()).text(this.getTrigger().attr("name"));
@@ -233,6 +253,20 @@ $(document).ready(function(){
windowTitle();
if ($("#order > ol > li.group").length == 1) { orderGroup(); };
+
+ function findElementByHash(locationHash) {
+ var temp = locationHash.replace('#', '');
+ var memberSelector = '#' + escapeJquery(temp);
+ return $(memberSelector);
+ }
+
+ // highlight and jump to selected member
+ if (window.location.hash) {
+ var jqElem = findElementByHash(window.location.hash);
+ if (jqElem.length > 0) {
+ exposeMember(jqElem);
+ }
+ }
});
function orderAlpha() {
diff --git a/test/scaladoc/resources/SI-8144.scala b/test/scaladoc/resources/SI-8144.scala
new file mode 100644
index 0000000000..7b225acb32
--- /dev/null
+++ b/test/scaladoc/resources/SI-8144.scala
@@ -0,0 +1,17 @@
+package some.pack
+
+class SomeType(arg: String) {
+
+ type TypeAlias = String
+
+ def >@<(): TypeAlias = "Tricky method name"
+
+ def >#<(): Int = 1
+
+}
+
+object SomeType {
+
+ val someVal = "Some arbitrary companion object value"
+
+}
diff --git a/test/scaladoc/scalacheck/HtmlFactoryTest.scala b/test/scaladoc/scalacheck/HtmlFactoryTest.scala
index 56328ea875..4205bad54e 100644
--- a/test/scaladoc/scalacheck/HtmlFactoryTest.scala
+++ b/test/scaladoc/scalacheck/HtmlFactoryTest.scala
@@ -149,7 +149,6 @@ object Test extends Properties("HtmlFactory") {
result
}
-
def shortComments(root: scala.xml.Node) =
XMLUtil.stripGroup(root).descendant.flatMap {
case e: scala.xml.Elem => {
@@ -417,7 +416,7 @@ object Test extends Properties("HtmlFactory") {
checkText("SI_5054_q1.scala")(
(None,"""def test(): Int""", true)
//Disabled because the full signature is now displayed
- //(None,"""def test(implicit lost: Int): Int""", false)
+ //(None, """def test(implicit lost: Int): Int""", false)
)
property("SI-5054: Use cases should keep their flags - final should not be lost") =
@@ -564,7 +563,7 @@ object Test extends Properties("HtmlFactory") {
property("Comment inheritance: Correct explicit inheritance for override") =
checkText("explicit-inheritance-override.scala")(
(Some("InheritDocDerived"),
- """def function[T](arg1: T, arg2: String): Double
+ """def function[T](arg1: T, arg2: String): Double
Starting line
Starting line
The base comment. And another sentence...
@@ -591,7 +590,7 @@ object Test extends Properties("HtmlFactory") {
property("Comment inheritance: Correct explicit inheritance for usecase") =
checkText("explicit-inheritance-usecase.scala")(
(Some("UseCaseInheritDoc"),
- """def function[T](arg1: T, arg2: String): Double
+ """def function[T](arg1: T, arg2: String): Double
[use case] Starting line
[use case] Starting line
The base comment. And another sentence...
@@ -741,4 +740,63 @@ object Test extends Properties("HtmlFactory") {
case _ => false
}
}
+
+ // SI-8144
+ {
+ implicit class AttributesAwareNode(val node: NodeSeq) {
+
+ def \@(attrName: String): String =
+ node \ ("@" + attrName) text
+
+ def \@(attrName: String, attrValue: String): NodeSeq =
+ node filter { _ \ ("@" + attrName) exists (_.text == attrValue) }
+ }
+
+ implicit class AssertionAwareNode(node: scala.xml.NodeSeq) {
+
+ def assertTypeLink(expectedUrl: String): Boolean = {
+ val linkElement: NodeSeq = node \\ "div" \@ ("id", "definition") \\ "span" \@ ("class", "permalink") \ "a"
+ linkElement \@ "href" == expectedUrl && linkElement \@ "target" == "_top"
+ }
+
+ def assertMemberLink(group: String)(memberName: String, expectedUrl: String): Boolean = {
+ val linkElement: NodeSeq = node \\ "div" \@ ("id", group) \\ "li" \@ ("name", memberName) \\ "span" \@ ("class", "permalink") \ "a"
+ linkElement \@ "href" == expectedUrl && linkElement \@ "target" == "_top"
+ }
+
+ }
+
+ val files = createTemplates("SI-8144.scala")
+
+ def check(pagePath: String)(f: NodeSeq => org.scalacheck.Prop): org.scalacheck.Prop =
+ files(pagePath) match {
+ case node: scala.xml.Node => f(XMLUtil.stripGroup(node))
+ case _ => false
+ }
+
+ property("SI-8144: Members' permalink - package") = check("some/package.html") { node =>
+ ("type link" |: node.assertTypeLink("../index.html#some.package")) &&
+ ("member: some.pack" |: node.assertMemberLink("values")("some.pack", "../index.html#some.package@pack"))
+ }
+
+ property("SI-8144: Members' permalink - inner package") = check("some/pack/package.html") { node =>
+ ("type link" |: node.assertTypeLink("../../index.html#some.pack.package")) &&
+ ("member: SomeType (object)" |: node.assertMemberLink("values")("some.pack.SomeType", "../../index.html#some.pack.package@SomeType")) &&
+ ("member: SomeType (class)" |: node.assertMemberLink("types")("some.pack.SomeType", "../../index.html#some.pack.package@SomeTypeextendsAnyRef"))
+ }
+
+ property("SI-8144: Members' permalink - companion object") = check("some/pack/SomeType$.html") { node =>
+ ("type link" |: node.assertTypeLink("../../index.html#some.pack.SomeType$")) &&
+ ("member: someVal" |: node.assertMemberLink("allMembers")("some.pack.SomeType#someVal", "../../index.html#some.pack.SomeType$@someVal:String"))
+ }
+
+ property("SI-8144: Members' permalink - class") = check("some/pack/SomeType.html") { node =>
+ ("type link" |: node.assertTypeLink("../../index.html#some.pack.SomeType")) &&
+ ("constructor " |: node.assertMemberLink("constructors")("some.pack.SomeType#<init>", "../../index.html#some.pack.SomeType@<init>(arg:String):some.pack.SomeType")) &&
+ ( "member: type TypeAlias" |: node.assertMemberLink("types")("some.pack.SomeType.TypeAlias", "../../index.html#some.pack.SomeType@TypeAlias=String")) &&
+ ( "member: def >#<():Int " |: node.assertMemberLink("values")("some.pack.SomeType#>#<", "../../index.html#some.pack.SomeType@>#<():Int")) &&
+ ( "member: def >@<():TypeAlias " |: node.assertMemberLink("values")("some.pack.SomeType#>@<", "../../index.html#some.pack.SomeType@>@<():SomeType.this.TypeAlias"))
+ }
+
+ }
}