diff --git a/scaladoc-js/resources/versions-dropdown.css b/scaladoc-js/resources/versions-dropdown.css index 94f0359b4108..5e3e8236137b 100644 --- a/scaladoc-js/resources/versions-dropdown.css +++ b/scaladoc-js/resources/versions-dropdown.css @@ -1,14 +1,23 @@ /* The container
- needed to position the dropdown content */ + .versions-dropdown { + margin-left: 10px; + margin-right: 10px; position: relative; } /* Dropdown Button */ .dropdownbtn { - background-color: var(--leftbar-bg); - color: white; padding: 4px 12px; border: none; + display: flex; + flex-direction: row; + align-items: center; + border-radius: 3px; +} + +.dropdownbtnactive { + background-color: var(--leftbar-dropdown-bg); } /* Dropdown button on hover & focus */ @@ -17,43 +26,75 @@ cursor: pointer; } +.dropdownbtn span.ar { + display: none; +} + +.dropdownbtnactive span.ar { + display: unset; + position:absolute; + right: 10px; + z-index: 100; +} + +.dropdownbtnactive span.ar:before { + content: '\e903'; +} + +.dropdownbtnactive.expanded span.ar:before { + content: '\e905'; +} + /* The search field */ #dropdown-input { box-sizing: border-box; - background-image: url('searchicon.png'); - background-position: 14px 12px; - background-repeat: no-repeat; - font-size: 16px; - padding: 14px 20px 12px 45px; - border: none; - border-bottom: 1px solid #ddd; + background-color: var(--leftbar-dropdown-bg); + color: var(--leftbar-fg); + width: 100%; + font-size: var(--leftbar-font-size); + border-radius: 3px; + padding: 4px 12px; } +#dropdown-input::placeholder{ + color: var(--inactive-fg); +} -/* The search field when it gets focus/clicked on */ -#dropdown-input:focus {outline: 3px solid #ddd;} +#dropdown-input, #dropdown-input:focus { + outline: none; + border: none; + border-bottom: 1px solid var(--border-medium); +} /* Dropdown Content (Hidden by Default) */ .dropdown-content { display: none; position: absolute; - background-color: #f6f6f6; + top: 0px; + left: 0px; + width: 100%; + background-color: var(--leftbar-dropdown-bg); + font-size: var(--leftbar-font-size); min-width: 230px; - border: 1px solid #ddd; + border-radius: 3px; z-index: 1; } /* Links inside the dropdown */ .dropdown-content a { - color: black; - padding: 12px 16px; + color: var(--leftbar-fg); text-decoration: none; + padding: 4px 12px; display: block; + border-radius: 3px; } /* Change color of dropdown links on hover */ -.dropdown-content a:hover {background-color: #f1f1f1} +.dropdown-content a:hover { + background-color: var(--leftbar-hover-bg); + color: var(--leftbar-fg); +} /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ .show { diff --git a/scaladoc-js/src/versions-dropdown/DropdownHandler.scala b/scaladoc-js/src/versions-dropdown/DropdownHandler.scala index 01ea7df6548a..53187cf066ab 100644 --- a/scaladoc-js/src/versions-dropdown/DropdownHandler.scala +++ b/scaladoc-js/src/versions-dropdown/DropdownHandler.scala @@ -60,14 +60,11 @@ class DropdownHandler: addVersionsList(json) document.addEventListener("click", (e: Event) => { - if e.target.asInstanceOf[html.Element].id != "dropdown-button" then - document.getElementById("dropdown-content").classList.remove("show") - document.getElementById("dropdown-button").classList.remove("expanded") + document.getElementById("dropdown-content").classList.remove("show") + document.getElementById("dropdown-button").classList.remove("expanded") }) - document.getElementById("version").asInstanceOf[html.Span].onclick = (e: Event) => { - e.stopPropagation - } + document.getElementById("version").asInstanceOf[html.Span].addEventListener("click", (e: Event) => e.stopPropagation()) end DropdownHandler @JSExportTopLevel("dropdownHandler") @@ -76,6 +73,7 @@ def dropdownHandler() = window.getSelection.toString.length == 0 then document.getElementById("dropdown-content").classList.toggle("show") document.getElementById("dropdown-button").classList.toggle("expanded") + document.getElementById("dropdown-input").asInstanceOf[html.Input].focus() @JSExportTopLevel("filterFunction") def filterFunction() = diff --git a/scaladoc-testcases/src/example/level2/Documentation.scala b/scaladoc-testcases/src/example/level2/Documentation.scala index e6f8a3f73f98..cd8e13451df8 100644 --- a/scaladoc-testcases/src/example/level2/Documentation.scala +++ b/scaladoc-testcases/src/example/level2/Documentation.scala @@ -134,6 +134,21 @@ abstract class Documentation[T, A <: Int, B >: String, -X, +Y](c1: String, val c def useOfOutsideTypeInsideObject(): ReturnObjectWithType.returnType = ??? def useOfSameLevelOutsideType(): SameLevelTypeLinking = ??? + /** Lorem ipsum + * + * + * + * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur scelerisque facilisis sapien a lobortis. Fusce ultricies erat ante, sit amet bibendum orci commodo in. Sed elementum tempus ipsum id sodales. Ut quis nisi vitae turpis lacinia mattis id nec orci. Nullam tincidunt accumsan nisl, ac maximus quam eleifend tincidunt. Nunc ipsum nulla, mattis vitae auctor blandit, euismod sit amet elit. Proin sed porttitor nisi. Curabitur tristique pretium nisi. Vestibulum sagittis condimentum blandit. In ac consequat odio, in fermentum turpis. In hac habitasse platea dictumst. + * Proin scelerisque est sed magna fermentum, at ullamcorper purus porta. Aliquam posuere arcu elit, molestie fermentum justo malesuada non. In eget massa risus. Proin rutrum maximus arcu, et lacinia est suscipit nec. Morbi varius odio pretium turpis ornare, ut sollicitudin nunc egestas. Aliquam pulvinar massa odio, id tempor purus suscipit id. Nunc imperdiet sapien ligula, ut pretium lacus efficitur sit amet. Sed sed urna sed erat tempus sagittis quis eget elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Duis accumsan hendrerit nunc, in sagittis tellus. Vivamus mattis ligula sed dolor lacinia iaculis. Pellentesque vel turpis est. Nam pellentesque diam id arcu pharetra, et consectetur eros facilisis. Aliquam erat volutpat. + * + * Phasellus ac quam pretium, convallis dui id, vestibulum nibh. Fusce vulputate interdum ullamcorper. Sed nec erat varius, sagittis ipsum eget, interdum ex. Ut sed leo sit amet ligula ullamcorper facilisis et convallis tellus. Nullam consectetur vitae lorem vel mattis. Suspendisse ultrices ornare leo, ut porttitor est finibus vel. Ut faucibus arcu eget sapien lobortis, a luctus arcu posuere. Vivamus faucibus mauris facilisis enim ornare dapibus. + * + * Quisque pharetra et orci non aliquet. Sed urna ipsum, commodo et ultricies sed, volutpat at nunc. Cras non lectus ac mauris lobortis efficitur vel ac ante. Mauris vestibulum risus at mauris pretium, vel iaculis dolor pretium. Nam fringilla fermentum lacus et varius. Nulla pulvinar maximus tortor, et venenatis ipsum luctus id. Integer hendrerit tellus felis, eget hendrerit dolor aliquam sit amet. + * + * Aenean elementum risus sed enim egestas, vitae imperdiet urna eleifend. Donec elementum leo neque, eu consequat eros placerat vel. Integer pulvinar sem feugiat, tincidunt erat a, porta nulla. Mauris eu urna egestas, facilisis ex sodales, sollicitudin quam. Integer porta metus et nunc blandit lacinia. Integer posuere mauris et dui ornare, a finibus neque tristique. Cras sit amet lectus nunc. Nam facilisis tincidunt efficitur. + */ + def loremIpsum[T](a: T): Map[T, T] = ??? + protected[example] val valWithScopeModifier = ??? protected[this] val valWithScopeModifierThis = ??? diff --git a/scaladoc/resources/dotty_res/scripts/ux.js b/scaladoc/resources/dotty_res/scripts/ux.js index 06616ba12b06..2a631e987bd0 100644 --- a/scaladoc/resources/dotty_res/scripts/ux.js +++ b/scaladoc/resources/dotty_res/scripts/ux.js @@ -20,6 +20,8 @@ window.addEventListener("DOMContentLoaded", () => { $(this).parent().toggleClass("expanded") }); + document.querySelectorAll("#sideMenu2 a").forEach(elem => elem.addEventListener('click', e => e.stopPropagation())) + $('.names .tab').on('click', function() { parent = $(this).parents(".tabs").first() shown = $(this).hasClass('selected') @@ -56,6 +58,19 @@ window.addEventListener("DOMContentLoaded", () => { window.location = pathToRoot; // global variable pathToRoot is created by the html renderer }; } + + document.querySelectorAll('.documentableAnchor').forEach(elem => { + elem.addEventListener('click', event => { + var $temp = $("") + $("body").append($temp) + var a = document.createElement('a') + a.href = $(elem).attr("link") + $temp.val(a.href).select(); + document.execCommand("copy") + $temp.remove(); + }) + }) + hljs.registerLanguage("scala", highlightDotty); hljs.registerAliases(["dotty", "scala3"], "scala"); hljs.initHighlighting(); diff --git a/scaladoc/resources/dotty_res/styles/colors.css b/scaladoc/resources/dotty_res/styles/colors.css index b0f5aff50226..1ef13486bb6c 100644 --- a/scaladoc/resources/dotty_res/styles/colors.css +++ b/scaladoc/resources/dotty_res/styles/colors.css @@ -14,7 +14,9 @@ --grey600: hsl(193, 14%, 52%); --grey700: hsl(193, 14%, 42%); --grey800: hsl(193, 12%, 28%); + --grey850: hsl(193, 12%, 22%); --grey900: hsl(193, 12%, 16%); + --grey930: hsl(193, 11%, 14%); /* Blue */ --blue100: hsl(200, 64%, 92%); @@ -73,6 +75,9 @@ --leftbar-current-fg: var(--blue500); --leftbar-hover-bg: var(--blue100); --leftbar-hover-fg: var(--grey900); + --leftbar-border: var(--grey300); + + --leftbar-dropdown-bg: var(--grey200); --footer-bg: var(--white); --footer-fg: var(--grey700); @@ -90,43 +95,46 @@ :root.theme-dark { color-scheme: dark; - --border-light: var(--blue800); - --border-medium: var(--blue700); + --border-light: var(--grey800); + --border-medium: var(--grey600); - --body-bg: var(--blue900); + --body-bg: var(--grey930); --body-fg: var(--grey300); - --title-fg: var(--blue200); + --title-fg: var(--grey200); --active-bg: var(--blue500); --active-bg-shadow: var(--blue400); --active-fg: var(--grey300); - --inactive-bg: var(--grey800); - --inactive-bg-shadow: var(--grey600); - --inactive-fg: var(--grey600); + --inactive-bg: var(--grey700); + --inactive-bg-shadow: var(--grey500); + --inactive-fg: var(--grey500); - --code-bg: var(--blue800); + --code-bg: var(--grey850); --code-fg: var(--grey400); --symbol-fg: var(--grey300); - --documentable-bg: var(--blue800); + --documentable-bg: var(--grey850); --link-fg: var(--blue400); --link-hover-fg: var(--blue300); --link-sig-fg: var(--blue400); - --leftbar-bg: var(--black); + --leftbar-bg: var(--grey930); --leftbar-fg: var(--grey300); - --leftbar-current-bg: var(--blue700); + --leftbar-current-bg: var(--grey700); --leftbar-current-fg: var(--white); - --leftbar-hover-bg: var(--blue800); + --leftbar-hover-bg: var(--grey800); --leftbar-hover-fg: var(--grey300); + --leftbar-border: var(--grey800); + + --leftbar-dropdown-bg: var(--grey850); - --footer-bg: var(--blue900); + --footer-bg: var(--grey930); --footer-fg: var(--grey400); --icon-color: var(--grey600); - --selected-fg: var(--blue800); - --selected-bg: var(--blue200); + --selected-fg: var(--grey800); + --selected-bg: var(--grey200); --tab-selected: var(--white); --tab-default: var(--grey300); diff --git a/scaladoc/resources/dotty_res/styles/filter-bar.css b/scaladoc/resources/dotty_res/styles/filter-bar.css index 28ec8d305db8..afa16658aeb3 100644 --- a/scaladoc/resources/dotty_res/styles/filter-bar.css +++ b/scaladoc/resources/dotty_res/styles/filter-bar.css @@ -1,6 +1,6 @@ .documentableFilter { padding: 24px 24px 24px 12px; - background-color: var(--code-bg); + background-color: var(--documentable-bg); } .documentableFilter.active .filterToggleButton svg { diff --git a/scaladoc/resources/dotty_res/styles/scalastyle.css b/scaladoc/resources/dotty_res/styles/scalastyle.css index 4a9c6fadd91c..747bf7cd5e8b 100644 --- a/scaladoc/resources/dotty_res/styles/scalastyle.css +++ b/scaladoc/resources/dotty_res/styles/scalastyle.css @@ -34,18 +34,25 @@ body, button, input { width: var(--side-width); height: 100%; border-right: none; - background: var(--leftbar-bg); + background: var(--body-bg); display: flex; flex-direction: column; z-index: 5; + border-right: solid 1px var(--leftbar-border); } -main { +#main-content { min-height: calc(100vh - var(--footer-height) - 24px); -} -#content { margin-left: var(--side-width); padding: var(--content-padding); padding-bottom: calc(24px + var(--footer-height)); + + display:flex; + flex-direction: column; + align-items: center; +} +#content { + max-width: 1100px; + width: 100%; } /* Text */ @@ -96,7 +103,6 @@ a, a:visited, span[data-unresolved-link] { } a:hover, a:active { color: var(--link-hover-fg); - text-decoration: underline; } /* Tables */ @@ -143,7 +149,6 @@ th { } #logo .projectVersion { - color: var(--grey600); font-size: 12px; display: flex; padding-left: 2px; @@ -171,33 +176,44 @@ th { /* Navigation */ #sideMenu2 { - overflow: auto; + overflow-y: scroll; overflow-x: hidden; + height: 100%; font-size: var(--leftbar-font-size); margin-top: 8px; - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; } -#sideMenu2::-webkit-scrollbar { - display: none; +/* divs in sidebar represent entry and its children */ +#sideMenu2 .ni { + padding-left: 0.8em; } -/* divs in sidebar represent entry and its children */ -#sideMenu2 div { - position: relative; +#sideMenu2 > .ni { + padding-left: 0em; +} + +#sideMenu2 .ni { display: none; - padding-left: 0.8em; } -#sideMenu2 div.expanded { +#sideMenu2 > .ni { display: block; } -/* hide children of hidden entries even if are expanded */ -#sideMenu2 div>div.expanded { - display: none; +#sideMenu2 .nh a:only-child { + padding-left: 20px; +} + +#sideMenu2 .nh { + display: flex; + flex-direction: row; + align-items: center; + border-radius: 3px; +} + +#sideMenu2 .ni.expanded > .ni { + display: block; } /* show direct children of currently exmanded node*/ @@ -219,18 +235,15 @@ th { #sideMenu2 a { display: flex; align-items: center; - flex: 1; overflow-wrap: anywhere; color: var(--leftbar-fg); - width: calc(2 * var(--side-width)); - margin-right: .5rem; - margin-left: calc(0px - var(--side-width)); padding-top: 2%; padding-bottom: 2%; - padding-left: calc(1.015 * var(--side-width)); - padding-right: calc(0.15 * var(--side-width)); box-sizing: border-box; - text-decoration: none; +} + +#sideMenu2 a:hover { + color: var(--link-hover-fg); } #sideMenu2 a span:not(.micon) { @@ -243,28 +256,24 @@ th { margin-right: 0.5ex; } -#sideMenu2 a.selected { +#sideMenu2 .nh.selected { background: var(--leftbar-current-bg); color: var(--leftbar-current-fg); font-weight: bold; } -#sideMenu2 a:hover { +#sideMenu2 .nh:hover { color: var(--leftbar-hover-fg); background: var(--leftbar-hover-bg); + cursor: pointer; } - /* spans represent a expand button */ span.ar { - align-items: center; cursor: pointer; - position: absolute; - right: 0.6em; - top: calc(0.01 * var(--side-width)); } span.ar::before { - content: "\e903"; /* arrow down */ + content: "\e904"; /* arrow right */ font-family: "dotty-icons" !important; font-size: 20px; color: var(--icon-color); @@ -273,8 +282,8 @@ span.ar::before { align-items: center; justify-content: center; } -.expanded>span.ar::before { - content: "\e905"; /* arrow up */ +.ni.expanded > .nh > span.ar::before { + content: "\e903"; /* arrow down */ } .div:hover>span.ar::before { @@ -407,7 +416,6 @@ dl > div > ol { dl.attributes > dt { display: block; float: left; - font-style: italic; font-weight: bold; } dl.attributes > dt.implicit { @@ -416,9 +424,8 @@ dl.attributes > dt.implicit { } dl.attributes > dd { display: block; - padding-left: 4em; - margin-bottom: 5px; - min-height: 15px; + padding-left: 7em; + min-height: 24px; } /* params list documentation */ @@ -575,10 +582,6 @@ footer .mode { color: var(--grey600); } -.kind { - font-weight: bold; -} - .other-modifiers a, .other-modifiers a:visited, .other-modifiers span[data-unresolved-link] { color: var(--link-sig-fg); } @@ -589,13 +592,23 @@ footer .mode { .documentableElement .signature { color: var(--code-fg); - display: table-cell; + display: table-row; white-space: pre-wrap; + font-size: 14px; +} + +.docs .modifiers { + font-size: 14px; +} + +.signature, .documentableElement { + font-weight: 500; } .signature.monospace { padding: 8px; display: flex; + flex-direction: column; border-radius: 3px; } @@ -607,8 +620,12 @@ footer .mode { color: var(--link-sig-fg); } +.signature a:hover { + color: var(--link-hover-fg); +} + .expand .signature { - display: table-cell; + display: table-row; } .documentableFilter { @@ -620,7 +637,6 @@ footer .mode { white-space: normal; position: relative; padding: 8px; - font-weight: 500; font-size: 12px; background: var(--documentable-bg); border-left: 0.25em solid transparent; @@ -658,7 +674,8 @@ footer .mode { } .documentableElement .annotations { color: var(--grey600); - margin-left: 10em; + margin-left: 10.5em; + font-size: 14px; display: none; } @@ -670,27 +687,50 @@ footer .mode { display: none; } +.documentableElement:before { + content: ' '; + position: absolute; + width: 32px; + height: 100%; + top: 0px; + left: -32px; +} + .documentableElement:hover .documentableAnchor:before { display: flex; } +.documentableElement::before:hover .documentableAnchor:before { + display: flex; +} + .documentableAnchor:before { content: "\e901"; /* arrow down */ font-family: "dotty-icons" !important; transform: rotate(-45deg); font-size: 20px; - color: var(--icon-color); + color: var(--link-fg); display: none; flex-direction: row; align-items: center; justify-content: center; position: absolute; top: 6px; - margin-left: 0.2em; + left: -32px; +} + +.documentableAnchor:hover:before { + color: var(--link-hover-fg); +} + +.documentableAnchor:active:before { + color: var(--link-hover-fg); + top: 8px; } .memberDocumentation { - font-size: 14px; + font-size: 15px; + line-height: 1.5; } .memberDocumentation>p{ @@ -823,6 +863,70 @@ footer .socials { align-items: center; } +/* Scrollbars */ + +:root { + --scrollbar-bg-color: var(--border-light); + --scrollbar-fg-color: var(--border-medium); + --scrollbar-fg-hover-color: var(--grey500); +} + +/* For Firefox */ +#sideMenu2 { + scrollbar-color: var(--scrollbar-fg-color) var(--scrollbar-bg-color); + scrollbar-width: thin; +} + +/* For Chrome */ + +#sideMenu2::-webkit-scrollbar-track { + background-color: var(--scrollbar-bg-color); + border-radius: 3px; +} + +#sideMenu2::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-fg-color); + border-radius: 3px; +} + +#sideMenu2::-webkit-scrollbar-thumb:hover { + background-color: var(--scrollbar-fg-hover-color); +} + +#sideMenu2::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +/* Signature coloring */ + +:root { + --type-link: var(--blue500); + --type: var(--grey700); + --keyword: var(--red500); +} + +:root.theme-dark { + --type-link: var(--blue400); + --keyword: var(--red400); +} + +.signature *[t="t"]:not([href]) { /* Types without links */ + color: var(--type); +} + +.signature *[t="t"] { /* Types with links */ + color: var(--type-link); +} + +.signature *[t="k"] { /* Keywords */ + color: var(--keyword); +} + +.signature *:not[t] { /* Plain text */ + +} + /* Large Screens */ @media(min-width: 1100px) { :root { @@ -927,4 +1031,8 @@ footer .socials { margin: 0 8px 0 0; } +.breadcrumbs { + align-self: flex-start; +} + diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index b96e3f219740..1b3248bfecee 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -57,7 +57,7 @@ enum Kind(val name: String): case Trait(typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList]) extends Kind("trait") with Classlike case Enum(typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList]) extends Kind("enum") with Classlike - case EnumCase(kind: Object.type | Type | Val.type | Class) extends Kind("case") + case EnumCase(kind: Object.type | Kind.Type | Val.type | Class) extends Kind("case") case Def(typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList]) extends Kind("def") case Extension(on: ExtensionTarget, m: Kind.Def) extends Kind("def") @@ -117,15 +117,19 @@ case class TypeParameter( signature: Signature ) -// TODO (longterm) properly represent signatures case class Link(name: String, dri: DRI) -type Signature = Seq[String | Link] -object Signature: - def apply(names: (String | Link)*): Signature = names // TO batter dotty shortcommings in union types +sealed trait SignaturePart -extension (s: Signature) - def join(a: Signature): Signature = s ++ a +// TODO (longterm) properly represent signatures +case class Type(name: String, dri: Option[DRI]) extends SignaturePart +case class Keyword(name: String) extends SignaturePart +case class Plain(txt: String) extends SignaturePart + +type Signature = List[SignaturePart] + +object Signature: + def apply(names: (SignaturePart)*): Signature = names.toList case class LinkToType(signature: Signature, dri: DRI, kind: Kind) @@ -230,8 +234,9 @@ extension (m: Module) extension (s: Signature) def getName: String = s.map { - case s: String => s - case l: Link => l.name + case Plain(s) => s + case Type(s, _) => s + case Keyword(s) => s }.mkString case class TastyMemberSource(path: java.nio.file.Path, lineNumber: Int) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index f0aacccff7a4..8db1dc2e5b87 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -198,7 +198,8 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx def renderNested(nav: Page, toplevel: Boolean = false): (Boolean, AppliedTag) = val isSelected = nav.link.dri == pageLink.dri - def linkHtml(expanded: Boolean = false) = + + def linkHtml(expanded: Boolean = false, withArrow: Boolean = false) = val attrs: Seq[String] = Seq( Option.when(isSelected)("selected"), Option.when(expanded)("expanded") @@ -207,18 +208,22 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx case m: Member => navigationIcon(m) case _ => Nil } - Seq(a(href := pathToPage(pageLink.dri, nav.link.dri), cls := attrs.mkString(" "))(icon, span(nav.link.name))) + Seq( + span(cls := "nh " + attrs.mkString(" "))( + if withArrow then Seq(span(cls := "ar")) else Nil, + a(href := pathToPage(pageLink.dri, nav.link.dri))(icon, span(nav.link.name)) + ) + ) nav.children match - case Nil => isSelected -> div(linkHtml()) + case Nil => isSelected -> div(cls := s"ni ${if isSelected then "expanded" else ""}")(linkHtml()) case children => val nested = children.map(renderNested(_)) - val expanded = nested.exists(_._1) || nav.link == pageLink + val expanded = nested.exists(_._1) || isSelected val attr = - if expanded || isSelected || toplevel then Seq(cls := "expanded") else Nil + if expanded || isSelected || toplevel then Seq(cls := "ni expanded") else Seq(cls := "ni") (isSelected || expanded) -> div(attr)( - linkHtml(expanded), - if toplevel then Nil else span(cls := "ar"), + linkHtml(expanded, true), nested.map(_._2) ) @@ -273,10 +278,10 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx div(id := "version")( div(cls := "versions-dropdown")( div(onclick := "dropdownHandler()", id := "dropdown-button", cls := "dropdownbtn dropdownbtnactive")( - args.projectVersion.map(v => div(cls:="projectVersion")(v)).getOrElse("") - ), - div(id := "dropdown-content", cls := "dropdown-content")( - input(`type` := "text", placeholder := "Search...", id := "dropdown-input", onkeyup := "filterFunction()"), + args.projectVersion.map(v => div(cls:="projectVersion")(v)).getOrElse(""), + div(id := "dropdown-content", cls := "dropdown-content")( + input(`type` := "text", placeholder := "Search...", id := "dropdown-input", onkeyup := "filterFunction()"), + ), ), ) ), @@ -294,11 +299,9 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx span(cls := "icon-toggler") ), div(id := "scaladoc-searchBar"), - main( - div(id := "content")( - parentsHtml, - div(content), - ) + main(id := "main-content")( + parentsHtml, + div(id := "content")(content), ), footer( div(id := "generated-by")( diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index d02b0bb7248a..a0ccada4b160 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -29,7 +29,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext case _ => Nil def inheritedFrom(m: Member) = m.inheritedFrom match - case Some(InheritedFrom(name, dri)) => tableRow("Inherited from", signatureRenderer.renderLink(name, dri)) + case Some(InheritedFrom(name, dri)) => tableRow("Inherited from:", signatureRenderer.renderLink(name, dri)) case _ => Nil def docAttributes(m: Member): Seq[AppliedTag] = @@ -46,31 +46,31 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext def opt(name: String, on: Option[DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else tableRow(name, renderDocPart(on.get)) - def authors(authors: List[DocPart]) = if summon[DocContext].args.includeAuthors then list("Authors", authors) else Nil + def authors(authors: List[DocPart]) = if summon[DocContext].args.includeAuthors then list("Authors:", authors) else Nil m.docs.fold(Nil)(d => - nested("Type Params", d.typeParams) ++ - nested("Value Params", d.valueParams) ++ - opt("Returns", d.result) ++ - nested("Throws", d.throws) ++ - opt("Constructor", d.constructor) ++ + nested("Type parameters:", d.typeParams) ++ + nested("Value parameters:", d.valueParams) ++ + opt("Returns:", d.result) ++ + nested("Throws:", d.throws) ++ + opt("Constructor:", d.constructor) ++ authors(d.authors) ++ - list("See also", d.see) ++ - opt("Version", d.version) ++ - opt("Since", d.since) ++ - list("Todo", d.todo) ++ - list("Note", d.note) ++ - list("Example", d.example) + list("See also:", d.see) ++ + opt("Version:", d.version) ++ + opt("Since:", d.since) ++ + list("Todo:", d.todo) ++ + list("Note:", d.note) ++ + list("Example:", d.example) ) def companion(m: Member): Seq[AppliedTag] = m.companion.fold(Nil){dri => val kindName = if m.kind == Kind.Object then "class" else "object" - tableRow("Companion", signatureRenderer.renderLink(kindName, dri)) + tableRow("Companion:", signatureRenderer.renderLink(kindName, dri)) } def source(m: Member): Seq[AppliedTag] = summon[DocContext].sourceLinks.pathTo(m).fold(Nil){ link => - tableRow("Source", a(href := link)(m.sources.fold("(source)")(_.path.getFileName().toString()))) + tableRow("Source:", a(href := link)(m.sources.fold("(source)")(_.path.getFileName().toString()))) } def deprecation(m: Member): Seq[AppliedTag] = m.deprecated.fold(Nil){ a => @@ -137,10 +137,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext val kind :: modifiersRevered = inlineBuilder.preName val signature = inlineBuilder.names.reverse Seq( - span(cls := "modifiers")( - span(cls := "other-modifiers")(modifiersRevered.reverse.map(renderElement)), - ), div(cls := "signature")( + span(cls := "modifiers")(modifiersRevered.reverse.map(renderElement)), span(cls := "kind")(renderElement(kind)), renderLink(member.name, member.dri, nameClasses), span(signature.map(renderElement)) @@ -167,7 +165,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext ++ filterAttributes.map{ case (n, v) => Attr(s"data-f-$n") := v } div(topLevelAttr:_*)( - a(href := (if member.needsOwnPage then link(member.dri).getOrElse("#") else s"#${member.dri.anchor}"), cls := "documentableAnchor"), + if !member.needsOwnPage then a(Attr("link") := link(member.dri).getOrElse("#"), cls := "documentableAnchor") else Nil, div(annotations(member)), div(cls := "header monospace")(memberSignature(member)), div(cls := "docs")( @@ -282,13 +280,13 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext }.collect { case (Some(on), members) => val typeSig = InlineSignatureBuilder() - .text("extension ") + .keyword("extension ") .generics(on.typeParams) .asInstanceOf[InlineSignatureBuilder].names.reverse val argsSig = InlineSignatureBuilder() .functionParameters(on.argsLists) .asInstanceOf[InlineSignatureBuilder].names.reverse - val sig = typeSig ++ Signature(s"(${on.name}: ") ++ on.signature ++ Signature(")") ++ argsSig + val sig = typeSig ++ Signature(Plain(s"(${on.name}: ")) ++ on.signature ++ Signature(Plain(")")) ++ argsSig MGroup(span(sig.map(renderElement)), members.sortBy(_.name).toSeq, on.name) }.toSeq diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index b40812849466..7dc40f68c322 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -127,8 +127,9 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: def searchData(pages: Seq[Page]) = def flattenToText(signature: Signature): String = signature.map { - case Link(name, dri) => name - case s: String => s + case Type(name, dri) => name + case Plain(s) => s + case Keyword(s) => s }.mkString def mkEntry(dri: DRI, name: String, text: String, descr: String, kind: String) = jsonObject( @@ -145,7 +146,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: val descr = m.dri.asFileLocation def processMember(member: Member): Seq[JSON] = val signatureBuilder = ScalaSignatureProvider.rawSignature(member, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] - val sig = Signature(member.kind.name, " ") ++ Seq(Link(member.name, member.dri)) ++ signatureBuilder.names.reverse + val sig = Signature(Plain(s"${member.kind.name} "), Plain(member.name)) ++ signatureBuilder.names.reverse val entry = mkEntry(member.dri, member.name, flattenToText(sig), descr, member.kind.name) val children = member .membersBy(m => m.kind != Kind.Package && !m.kind.isInstanceOf[Classlike]) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/SignatureRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/SignatureRenderer.scala index 4abc2090954c..59ed3a697813 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/SignatureRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/SignatureRenderer.scala @@ -15,7 +15,7 @@ trait SignatureRenderer: def currentDri: DRI def link(dri: DRI): Option[String] - def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) + def renderElement(e: SignaturePart) = renderElementWith(e) def renderLink(name: String, dri: DRI, modifiers: AppliedAttr*) = renderLinkContent(name, dri, modifiers:_*) @@ -28,7 +28,10 @@ trait SignatureRenderer: case Some(link) => a(href := link, modifiers)(content) case _ => unresolvedLink(content, modifiers:_*) - def renderElementWith(e: String | (String, DRI) | Link, modifiers: AppliedAttr*) = e match - case (name, dri) => renderLink(name, dri, modifiers:_*) - case name: String => raw(name) - case Link(name, dri) => renderLink(name, dri, modifiers:_*) + def renderElementWith(e: SignaturePart, modifiers: AppliedAttr*) = e match + case Type(name, Some(dri)) => + val attrs = Seq(Attr("t") := "t") ++ modifiers + renderLink(name, dri, attrs:_*) + case Type(name, None) => span(Attr("t") := "t")(name) + case Keyword(name) => span(Attr("t") := "k")(name) + case Plain(name) => raw(name) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 6cdca1590eae..09d71021a6f5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -435,7 +435,7 @@ trait ClassLikeSupport: val signature = contextBounds.get(name) match case None => boundsSignature case Some(contextBoundsSignature) => - boundsSignature ++ DSignature(" : ") ++ contextBoundsSignature + boundsSignature ++ DSignature(Plain(" : ")) ++ contextBoundsSignature TypeParameter( argument.symbol.getAnnotations(), @@ -546,8 +546,8 @@ trait ClassLikeSupport: case Nil => Left((name, original)) case typeParam :: _ => val name = nameForRef(typeParam) - val signature = Seq(s"([$name] =>> ") ++ original.asSignature ++ Seq(")") - Right(name -> signature) + val signature = Seq(Plain("(["), dotty.tools.scaladoc.Type(name, None), Plain("]"), Keyword(" =>> ")) ++ original.asSignature ++ Seq(Plain(")")) + Right(name -> signature.toList) } val newParams = notEvidences ++ paramsThatLookLikeContextBounds diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 91b4eb71b06b..48431cb18276 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -11,10 +11,9 @@ import SyntheticsSupport._ trait TypesSupport: self: TastyParser => - type DocSignaturePart = String | Link - type DocSignature = List[DocSignaturePart] + type SSignature = List[SignaturePart] - def getGivenInstance(method: qctx.reflect.DefDef): Option[DocSignature] = + def getGivenInstance(method: qctx.reflect.DefDef): Option[SSignature] = import qctx.reflect._ given qctx.type = qctx @@ -39,7 +38,7 @@ trait TypesSupport: given TreeSyntax: AnyRef with extension (using Quotes)(tpeTree: reflect.Tree) - def asSignature: DocSignature = + def asSignature: SSignature = import reflect._ tpeTree match case TypeBoundsTree(low, high) => typeBoundsTreeOfHigherKindedType(low.tpe, high.tpe) @@ -48,20 +47,26 @@ trait TypesSupport: given TypeSyntax: AnyRef with extension (using Quotes)(tpe: reflect.TypeRepr) - def asSignature: DocSignature = inner(tpe) + def asSignature: SSignature = inner(tpe) - private def text(str: String): DocSignaturePart = str + private def plain(str: String): SignaturePart = Plain(str) - private def texts(str: String): DocSignature = List(text(str)) + private def keyword(str: String): SignaturePart = Keyword(str) - private def link(using Quotes)(symbol: reflect.Symbol): DocSignature = - val suffix = if symbol.isValDef then texts(".type") else Nil - Link(symbol.normalizedName, symbol.dri) :: suffix + private def tpe(str: String, dri: DRI): SignaturePart = dotty.tools.scaladoc.Type(str, Some(dri)) - private def commas(lists: List[DocSignature]) = lists match + private def tpe(str: String): SignaturePart = dotty.tools.scaladoc.Type(str, None) + + extension (on: SignaturePart) def l: List[SignaturePart] = List(on) + + private def tpe(using Quotes)(symbol: reflect.Symbol): SSignature = + val suffix = if symbol.isValDef then plain(".type").l else Nil + dotty.tools.scaladoc.Type(symbol.normalizedName, Some(symbol.dri)) :: suffix + + private def commas(lists: List[SSignature]) = lists match case List(single) => single - case other => other.reduce((r, e) => r ++ texts(", ") ++ e) + case other => other.reduce((r, e) => r ++ plain(", ").l ++ e) private def isRepeatedAnnotation(using Quotes)(term: reflect.Term) = import reflect._ @@ -80,34 +85,34 @@ trait TypesSupport: case _ => false // TODO #23 add support for all types signatures that makes sense - private def inner(using Quotes)(tp: reflect.TypeRepr)(using indent: Int = 0): DocSignature = + private def inner(using Quotes)(tp: reflect.TypeRepr)(using indent: Int = 0): SSignature = import reflect._ - def noSupported(name: String): DocSignature = + def noSupported(name: String): SSignature = println(s"WARN: Unsupported type: $name: ${tp.show}") - List(text(s"Unsupported[$name]")) + plain(s"Unsupported[$name]").l tp match - case OrType(left, right) => inner(left) ++ texts(" | ") ++ inner(right) - case AndType(left, right) => inner(left) ++ texts(" & ") ++ inner(right) - case ByNameType(tpe) => text("=> ") :: inner(tpe) + case OrType(left, right) => inner(left) ++ keyword(" | ").l ++ inner(right) + case AndType(left, right) => inner(left) ++ keyword(" & ").l ++ inner(right) + case ByNameType(tpe) => keyword("=> ") :: inner(tpe) case ConstantType(constant) => - texts(constant.show) + plain(constant.show).l case ThisType(tpe) => inner(tpe) case AnnotatedType(AppliedType(_, Seq(tpe)), annotation) if isRepeatedAnnotation(annotation) => - inner(tpe) :+ text("*") + inner(tpe) :+ plain("*") case AppliedType(repeatedClass, Seq(tpe)) if isRepeated(repeatedClass) => - inner(tpe) :+ text("*") + inner(tpe) :+ plain("*") case AnnotatedType(tpe, _) => inner(tpe) case tl @ TypeLambda(params, paramBounds, resType@AppliedType(tpe, args)) if paramBounds.map(inner).forall(_.isEmpty) && params.zip(args.map(inner)).forall(List(_) == _) => inner(tpe) case tl @ TypeLambda(params, paramBounds, resType) => - texts("[") ++ commas(params.zip(paramBounds).map { (name, typ) => + plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - texts(normalizedName) ++ inner(typ) - }) ++ texts("]") - ++ texts(" =>> ") + tpe(normalizedName).l ++ inner(typ) + }) ++ plain("]").l + ++ keyword(" =>> ").l ++ inner(resType) @@ -117,91 +122,91 @@ trait TypesSupport: case t => List(t) } - def getParamBounds(t: PolyType): DocSignature = commas( + def getParamBounds(t: PolyType): SSignature = commas( t.paramNames.zip(t.paramBounds.map(inner(_))) - .map(b => texts(b(0)) ++ b(1)) + .map(b => tpe(b(0)).l ++ b(1)) ) - def getParamList(m: MethodType): DocSignature = - texts("(") - ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => texts(s"$name: ") ++ inner(tp)} - .reduceLeftOption((acc: DocSignature, elem: DocSignature) => acc ++ texts(", ") ++ elem).getOrElse(List()) - ++ texts(")") + def getParamList(m: MethodType): SSignature = + plain("(").l + ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => plain(s"$name: ").l ++ inner(tp)} + .reduceLeftOption((acc: SSignature, elem: SSignature) => acc ++ plain(", ").l ++ elem).getOrElse(List()) + ++ plain(")").l - def parseRefinedElem(name: String, info: TypeRepr, polyTyped: DocSignature = Nil): DocSignature = ( info match { + def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = ( info match { case m: MethodType => { val paramList = getParamList(m) - texts(s"def $name") ++ polyTyped ++ paramList ++ texts(": ") ++ inner(m.resType) + keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType) } case t: PolyType => { val paramBounds = getParamBounds(t) val parsedMethod = parseRefinedElem(name, t.resType) if (!paramBounds.isEmpty){ - parseRefinedElem(name, t.resType, texts("[") ++ paramBounds ++ texts("]")) + parseRefinedElem(name, t.resType, plain("[").l ++ paramBounds ++ plain("]").l) } else parseRefinedElem(name, t.resType) } - case ByNameType(tp) => texts(s"def $name: ") ++ inner(tp) - case t: TypeBounds => texts(s"type $name") ++ inner(t) - case t: TypeRef => texts(s"val $name: ") ++ inner(t) - case t: TermRef => texts(s"val $name: ") ++ inner(t) + case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp) + case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t) + case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) + case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t) case other => noSupported(s"Not supported type in refinement $info") - } ) ++ texts("; ") + } ) ++ plain("; ").l - def parsePolyFunction(info: TypeRepr): DocSignature = info match { + def parsePolyFunction(info: TypeRepr): SSignature = info match { case t: PolyType => val paramBounds = getParamBounds(t) val method = t.resType.asInstanceOf[MethodType] val paramList = getParamList(method) val resType = inner(method.resType) - texts("[") ++ paramBounds ++ texts("] => ") ++ paramList ++ texts(" => ") ++ resType + plain("[").l ++ paramBounds ++ plain("]").l ++ keyword(" => ").l ++ paramList ++ keyword(" => ").l ++ resType case other => noSupported(s"Not supported type in refinement $info") } val refinementInfo = getRefinementInformation(r) val refinedType = refinementInfo.head val refinedElems = refinementInfo.tail.collect{ case r: Refinement => r }.toList - val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ texts(" ") else Nil + val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ plain(" ").l else Nil if (refinedType.typeSymbol.fullName == "scala.PolyFunction" && refinedElems.size == 1) { parsePolyFunction(refinedElems.head.info) } else { - prefix ++ texts("{ ") ++ refinedElems.flatMap(e => parseRefinedElem(e.name, e.info)) ++ texts(" }") + prefix ++ plain("{ ").l ++ refinedElems.flatMap(e => parseRefinedElem(e.name, e.info)) ++ plain(" }").l } } case t @ AppliedType(tpe, typeList) => import dotty.tools.dotc.util.Chars._ if !t.typeSymbol.name.forall(isIdentifierPart) && typeList.size == 2 then inner(typeList.head) - ++ texts(" ") + ++ plain(" ").l ++ inner(tpe) - ++ texts(" ") + ++ plain(" ").l ++ inner(typeList.last) else if t.isFunctionType then typeList match case Nil => Nil case Seq(rtpe) => - text("() => ") :: inner(rtpe) + plain("()").l ++ keyword(" => ").l ++ inner(rtpe) case Seq(arg, rtpe) => val partOfSignature = arg match - case byName: ByNameType => texts("(") ++ inner(byName) ++ texts(")") + case byName: ByNameType => plain("(").l ++ inner(byName) ++ plain(")").l case _ => inner(arg) - partOfSignature ++ texts(" => ") ++ inner(rtpe) + partOfSignature ++ keyword(" => ").l ++ inner(rtpe) case args => - texts("(") ++ commas(args.init.map(inner)) ++ texts(") => ") ++ inner(args.last) + plain("(").l ++ commas(args.init.map(inner)) ++ plain(")").l ++ keyword(" => ").l ++ inner(args.last) else if t.isTupleN then typeList match case Nil => Nil case args => - texts("(") ++ commas(args.map(inner)) ++ texts(")") - else inner(tpe) ++ texts("[") ++ commas(typeList.map { t => t match - case _: TypeBounds => texts("_") ++ inner(t) + plain("(").l ++ commas(args.map(inner)) ++ plain(")").l + else inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match + case _: TypeBounds => keyword("_").l ++ inner(t) case _ => inner(t) - }) ++ texts("]") + }) ++ plain("]").l case tp @ TypeRef(qual, typeName) => qual match { - case r: RecursiveThis => texts(s"this.$typeName") - case _: TypeRepr => link(tp.typeSymbol) + case r: RecursiveThis => tpe(s"this.$typeName").l + case _: TypeRepr => tpe(tp.typeSymbol) } // convertTypeOrBoundsToReference(reflect)(qual) match { // case TypeReference(label, link, xs, _) => TypeReference(typeName, link + "/" + label, xs, true) @@ -233,7 +238,7 @@ trait TypesSupport: case tr @ TermRef(qual, typeName) => tr.termSymbol.tree match case vd: ValDef => inner(vd.tpt.tpe) - case _ => link(tr.termSymbol) + case _ => tpe(tr.termSymbol) // convertTypeOrBoundsToReference(reflect)(qual) match { @@ -248,7 +253,7 @@ trait TypesSupport: // } // case _ => throw Exception("No match for type in conversion to Reference. This should not happen, please open an issue. " + tp) case TypeBounds(low, hi) => - if(low == hi) texts(" = ") ++ inner(low) + if(low == hi) keyword(" = ").l ++ inner(low) else typeBoundsTreeOfHigherKindedType(low, hi) case NoPrefix() => Nil @@ -258,25 +263,25 @@ trait TypesSupport: val spaces = " " * (indent) val casesTexts = cases.flatMap { case MatchCase(from, to) => - texts(caseSpaces + "case ") ++ inner(from) ++ texts(" => ") ++ inner(to)(using indent = indent + 2) ++ texts("\n") + keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l case TypeLambda(_, _, MatchCase(from, to)) => - texts(caseSpaces + "case ") ++ inner(from) ++ texts(" => ") ++ inner(to)(using indent = indent + 2) ++ texts("\n") + keyword(caseSpaces + "case ").l ++ inner(from) ++ keyword(" => ").l ++ inner(to)(using indent = indent + 2) ++ plain("\n").l } - inner(sc) ++ texts(" match {\n") ++ casesTexts ++ texts(spaces + "}") + inner(sc) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l - case ParamRef(TypeLambda(names, _, _), i) => texts(names.apply(i)) + case ParamRef(TypeLambda(names, _, _), i) => tpe(names.apply(i)).l - case ParamRef(m: MethodType, i) => texts(m.paramNames(i)) + case ParamRef(m: MethodType, i) => tpe(m.paramNames(i)).l case RecursiveType(tp) => inner(tp) case MatchCase(pattern, rhs) => - texts("case ") ++ inner(pattern) ++ texts(" => ") ++ inner(rhs) + keyword("case ").l ++ inner(pattern) ++ keyword(" => ").l ++ inner(rhs) case t: dotty.tools.dotc.core.Types.LazyRef => try { inner(t.ref(using ctx.compilerContext).asInstanceOf[TypeRepr]) } catch { - case e: AssertionError => texts("LazyRef(...)") + case e: AssertionError => tpe("LazyRef(...)").l } case tpe => @@ -287,9 +292,9 @@ trait TypesSupport: private def typeBound(using Quotes)(t: reflect.TypeRepr, low: Boolean) = import reflect._ val ignore = if (low) t.typeSymbol == defn.NothingClass else t.typeSymbol == defn.AnyClass - val prefix = text(if low then " >: " else " <: ") + val prefix = keyword(if low then " >: " else " <: ") t match { - case l: TypeLambda => prefix :: texts("(") ++ inner(l) ++ texts(")") + case l: TypeLambda => prefix :: plain("(").l ++ inner(l) ++ plain(")").l case p: ParamRef => prefix :: inner(p) case other if !ignore => prefix :: inner(other) case _ => Nil @@ -302,10 +307,10 @@ trait TypesSupport: high.match case TypeLambda(params, paramBounds, resType) => if resType.typeSymbol == defn.AnyClass then - texts("[") ++ commas(params.zip(paramBounds).map { (name, typ) => + plain("[").l ++ commas(params.zip(paramBounds).map { (name, typ) => val normalizedName = if name.matches("_\\$\\d*") then "_" else name - texts(normalizedName) ++ inner(typ) - }) ++ texts("]") + tpe(normalizedName).l ++ inner(typ) + }) ++ plain("]").l else regularTypeBounds(low, high) case _ => regularTypeBounds(low, high) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index a2ade47bbbb2..8bd0a571b7d7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -35,7 +35,7 @@ object ScalaSignatureProvider: case tpe: Kind.Type => typeSignature(tpe, documentable, builder) case Kind.Package => - builder.text("package ").name(documentable.name, documentable.dri) + builder.keyword("package ").name(documentable.name, documentable.dri) case Kind.RootPackage => builder case Kind.Unknown => @@ -43,7 +43,7 @@ object ScalaSignatureProvider: private def enumEntrySignature(member: Member, cls: Kind.Class, bdr: SignatureBuilder): SignatureBuilder = val withPrefixes: SignatureBuilder = bdr - .text("case ") + .keyword("case ") .name(member.name, member.dri) .generics(cls.typeParams) @@ -52,22 +52,22 @@ object ScalaSignatureProvider: private def enumPropertySignature(entry: Member, builder: SignatureBuilder): SignatureBuilder = val modifiedType = entry.signature.map { - case " & " => " with " + case Keyword(" & ") => Keyword(" with ") case o => o } builder - .text("case ") + .keyword("case ") .name(entry.name, entry.dri) - .text(" extends ") + .keyword(" extends ") .signature(modifiedType) private def parentsSignature(member: Member, builder: SignatureBuilder): SignatureBuilder = member.directParents match case Nil => builder case extendType :: withTypes => - val extendPart = builder.text(" extends ").signature(extendType.signature) - withTypes.foldLeft(extendPart)((bdr, tpe) => bdr.text(" with ").signature(tpe.signature)) + val extendPart = builder.keyword(" extends ").signature(extendType.signature) + withTypes.foldLeft(extendPart)((bdr, tpe) => bdr.keyword(" with ").signature(tpe.signature)) private def givenClassSignature(member: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = val prefixes = builder @@ -78,7 +78,7 @@ object ScalaSignatureProvider: member.kind match case Kind.Given(_, Some(instance), _) => prefixes - .text(": ") + .plain(": ") .signature(instance) case _ => prefixes @@ -123,16 +123,16 @@ object ScalaSignatureProvider: .generics(fun.typeParams) .functionParameters(fun.argsLists) - withSignature.text(":").text(" ").signature(extension.signature) + withSignature.plain(":").plain(" ").signature(extension.signature) private def givenMethodSignature(method: Member, body: Kind.Def, builder: SignatureBuilder): SignatureBuilder = method.kind match case Kind.Given(_, Some(instance), _) => - builder.text("given ") + builder.keyword("given ") .name(method.name, method.dri) - .text(": ") + .plain(": ") .signature(instance) case _ => - builder.text("given ").name(method.name, method.dri) + builder.keyword("given ").name(method.name, method.dri) private def methodSignature(method: Member, cls: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val bdr = builder @@ -141,7 +141,7 @@ object ScalaSignatureProvider: .generics(cls.typeParams) .functionParameters(cls.argsLists) if !method.kind.isInstanceOf[Kind.Constructor] then - bdr.text(": ").signature(method.signature) + bdr.plain(": ").signature(method.signature) else bdr private def typeSignature(tpe: Kind.Type, typeDef: Member, builder: SignatureBuilder): SignatureBuilder = @@ -150,25 +150,25 @@ object ScalaSignatureProvider: .name(typeDef.name, typeDef.dri) .generics(tpe.typeParams) if(!tpe.opaque){ - (if tpe.concreate then bdr.text(" = ") else bdr) + (if tpe.concreate then bdr.plain(" = ") else bdr) .signature(typeDef.signature) } else bdr private def givenPropertySignature(property: Member, builder: SignatureBuilder): SignatureBuilder = val bdr = builder - .text("given ") + .keyword("given ") .name(property.name, property.dri) property.kind match case Kind.Given(_, Some(instance), _) => - bdr.text(" as ").signature(instance) + bdr.keyword(" as ").signature(instance) case _ => bdr private def fieldSignature(member: Member, kind: String, builder: SignatureBuilder): SignatureBuilder = builder .modifiersAndVisibility(member, kind) .name(member.name, member.dri) - .text(":") - .text(" ") + .plain(":") + .plain(" ") .signature(member.signature) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index 8f67cb9ec9b9..7a5dc2310c0e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -2,9 +2,11 @@ package dotty.tools.scaladoc package translators case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = Nil) extends SignatureBuilder: - override def text(str: String): SignatureBuilder = copy(names = str +: names) + override def plain(str: String): SignatureBuilder = copy(names = Plain(str) +: names) override def name(str: String, dri: DRI): SignatureBuilder = copy(names = Nil, preName = names) - override def driLink(text: String, dri: DRI): SignatureBuilder = copy(names = Link(text, dri) +: names) + override def tpe(text: String, dri: Option[DRI]): SignatureBuilder = copy(names = Type(text, dri) +: names) + override def keyword(str: String): SignatureBuilder = copy(names = Keyword(str) +: names) + def tpe(text: String, dri: DRI): SignatureBuilder = copy(names = Type(text, Some(dri)) +: names) override def signature(s: Signature): SignatureBuilder = copy(names = s.reverse ++ names) object InlineSignatureBuilder: @@ -12,29 +14,31 @@ object InlineSignatureBuilder: ScalaSignatureProvider.rawSignature(d, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder].names.reverse trait SignatureBuilder extends ScalaSignatureUtils { - def text(str: String): SignatureBuilder - def name(str: String, dri: DRI) = driLink(str, dri) - def driLink(text: String, dri: DRI): SignatureBuilder + def keyword(str: String): SignatureBuilder + def plain(str: String): SignatureBuilder + def name (str: String, dri: DRI): SignatureBuilder + def tpe(str: String, dri: Option[DRI]): SignatureBuilder def signature(s: Signature): SignatureBuilder = s.foldLeft(this){ - case (bld, s: String) => bld.text(s) - case (bld, Link(text: String, dri: DRI)) => bld.driLink(text, dri) + case (bld, Type(a, b)) => bld.tpe(a, b) + case (bld, Keyword(a)) => bld.keyword(a) + case (bld, Plain(a)) => bld.plain(a) } // Support properly once we rewrite signature builder - def memberName(name: String, dri: DRI) = text(name) + def memberName(name: String, dri: DRI) = plain(name) def list[E]( elements: Seq[E], - prefix: String = "", - suffix: String = "", - separator: String = ", ", + prefix: Signature = List(Plain("")), + suffix: Signature = List(Plain("")), + separator: Signature = List(Plain(", ")), forcePrefixAndSuffix: Boolean = false )( elemOp: (SignatureBuilder, E) => SignatureBuilder ): SignatureBuilder = elements match { - case Nil => if forcePrefixAndSuffix then this.text(prefix + suffix) else this + case Nil => if forcePrefixAndSuffix then signature(prefix).signature(suffix) else this case head :: tail => - tail.foldLeft(elemOp(text(prefix), head))((b, e) => elemOp(b.text(separator), e)).text(suffix) + tail.foldLeft(elemOp(signature(prefix), head))((b, e) => elemOp(b.signature(separator), e)).signature(suffix) } def annotationsBlock(d: Member): SignatureBuilder = @@ -47,7 +51,7 @@ trait SignatureBuilder extends ScalaSignatureUtils { t.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } private def buildAnnotation(a: Annotation): SignatureBuilder = - text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ") + tpe(s"@${a.dri.location.split('.').last}", Some(a.dri)).buildAnnotationParams(a).plain(" ") private def buildAnnotationParams(a: Annotation): SignatureBuilder = if !a.params.isEmpty then @@ -55,41 +59,42 @@ trait SignatureBuilder extends ScalaSignatureUtils { case Annotation.LinkParameter(_, _, text) => text == "_" case _ => false } - list(params, "(", ")", ", "){ (bdr, param) => bdr.buildAnnotationParameter(param)} + list(params, List(Plain("(")), List(Plain(")")), List(Plain(", "))){ (bdr, param) => bdr.buildAnnotationParameter(param)} else this private def addParameterName(txt: Option[String]): SignatureBuilder = txt match { - case Some(name) => this.text(s"$name = ") + case Some(name) => this.plain(s"$name = ") case _ => this } private def buildAnnotationParameter(a: Annotation.AnnotationParameter): SignatureBuilder = a match { case Annotation.PrimitiveParameter(name, value) => - addParameterName(name).text(value) + addParameterName(name).plain(value) case Annotation.LinkParameter(name, dri, text) => - addParameterName(name).driLink(text, dri) + addParameterName(name).tpe(text, Some(dri)) case Annotation.UnresolvedParameter(name, value) => - addParameterName(name).text(value) + addParameterName(name).plain(value) } def modifiersAndVisibility(t: Member, kind: String) = val (prefixMods, suffixMods) = t.modifiers.partition(_.prefix) val all = prefixMods.map(_.name) ++ Seq(t.visibility.asSignature) ++ suffixMods.map(_.name) + val filtered = all.filter(_.trim.nonEmpty) + val intermediate = if filtered.nonEmpty then keyword(filtered.toSignatureString()) else this + intermediate.keyword(kind + " ") - text(all.toSignatureString()).text(kind + " ") - - def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) => - bdr.annotationsInline(e).text(e.variance).memberName(e.name, e.dri).signature(e.signature) + def generics(on: Seq[TypeParameter]) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => + bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri)).signature(e.signature) } def functionParameters(params: Seq[ParametersList]) = - if params.isEmpty then this.text("") - else if params.size == 1 && params(0).parameters == Nil then this.text("()") - else this.list(params, separator = "") { (bld, pList) => - bld.list(pList.parameters, s"(${pList.modifiers}", ")", forcePrefixAndSuffix = true) { (bld, p) => + if params.isEmpty then this.plain("") + else if params.size == 1 && params(0).parameters == Nil then this.plain("()") + else this.list(params, separator = List(Plain(""))) { (bld, pList) => + bld.list(pList.parameters, prefix = List(Plain("("), Keyword(pList.modifiers)), suffix = List(Plain(")")), forcePrefixAndSuffix = true) { (bld, p) => val annotationsAndModifiers = bld.annotationsInline(p) - .text(p.modifiers) - val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.memberName(_, p.dri).text(": ")) + .keyword(p.modifiers) + val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.memberName(_, p.dri).plain(": ")) name.signature(p.signature) } } diff --git a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala index b5590f51cbce..5285f996ec1d 100644 --- a/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala @@ -47,7 +47,7 @@ class BaseHtmlTest: def niceMsg(msg: String) = s"$msg in $path (body):\n ${d.html()}:\n" def assertTextsIn(selector: String, expected: String*) = - assertFalse(niceMsg("Selector not found"), d.select(selector).isEmpty) + assertFalse(niceMsg(s"Selector not found for '$selector'"), d.select(selector).isEmpty) val found = d.select(selector).eachText.asScala assertEquals(niceMsg(s"Context does not match for '$selector'"), expected.toList, found.toList) diff --git a/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala b/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala index 9a9556b868d7..eb27987f3f6c 100644 --- a/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala @@ -33,7 +33,7 @@ class AbstractMembers extends ScaladocTest("abstractmembersignatures"): val content = document.select(".documentableList").forEach { elem => val group = elem.select(".groupHeader").eachText.asScala.mkString("") elem.select(".documentableElement").forEach { elem => - val modifiers = elem.select(".header .other-modifiers").eachText.asScala.mkString("") + val modifiers = elem.select(".header .modifiers").eachText.asScala.mkString("") val name = elem.select(".header .documentableName").eachText.asScala.mkString("") signatures += group -> (modifiers, name) } diff --git a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala index 86ffb829db58..ebc74ef31561 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala @@ -9,16 +9,15 @@ class NavigationTest extends BaseHtmlTest: def testNavMenu(page: String, topLevel: NavMenuTestEntry)(using ProjectContext): Unit = withHtmlFile(page){ content => - def flatten(l: NavMenuTestEntry): Seq[NavMenuTestEntry] = l +: l.nested.flatMap(flatten) def test(query: String, el: Seq[NavMenuTestEntry]) = content.assertTextsIn(query, el.map(_.name):_*) content.assertAttr(query,"href", el.map(_.link):_*) - test("#sideMenu2 a", flatten(topLevel)) - test("#sideMenu2>div>div>a", topLevel.nested) - test("#sideMenu2>div>div>div>a", topLevel.nested.flatMap(_.nested)) - test("#sideMenu2>div>div>div>div>a", topLevel.nested.flatMap(_.nested.flatMap(_.nested))) + test("#sideMenu2>div>span>a", topLevel :: Nil) + test("#sideMenu2>div>div>span>a", topLevel.nested) + test("#sideMenu2>div>div>div>span>a", topLevel.nested.flatMap(_.nested)) + test("#sideMenu2>div>div>div>div>span>a", topLevel.nested.flatMap(_.nested.flatMap(_.nested))) }