Skip to content

Commit 9f088d7

Browse files
Merge pull request #12375 from KacperFKorban/scaladoc/inkuire
Scaladoc/inkuire
2 parents 158b332 + 1718d47 commit 9f088d7

26 files changed

+813
-46
lines changed

project/Build.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1267,7 +1267,7 @@ object Build {
12671267
scalaSrcLink(stdLibVersion, srcManaged(dottyNonBootstrappedVersion, "scala") + "="),
12681268
dottySrcLink(referenceVersion, srcManaged(dottyNonBootstrappedVersion, "dotty") + "=", "#library/src"),
12691269
dottySrcLink(referenceVersion),
1270-
) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets
1270+
) ++ scalacOptionsDocSettings ++ revision ++ params ++ targets ++ Seq("-Ygenerate-inkuire")
12711271
import _root_.scala.sys.process._
12721272
val escapedCmd = cmd.map(arg => if(arg.contains(" ")) s""""$arg"""" else arg)
12731273
Def.task {

scaladoc-js/resources/scaladoc-searchbar.css

+9-8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
box-shadow: 0 2px 16px 0 rgba(0, 42, 76, 0.15);
5151
font-size: 13px;
5252
font-family: system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif;
53+
background-color: var(--leftbar-bg);
54+
color: var(--leftbar-fg);
55+
box-shadow: 0 0 2px var(--shadow);
5356
}
5457

5558
#scaladoc-searchbar-input {
@@ -58,22 +61,24 @@
5861
border: none;
5962
border-bottom: 1px solid #bbb;
6063
padding: 10px;
64+
background-color: var(--leftbar-bg);
65+
color: var(--leftbar-fg);
6166
}
6267

6368
#scaladoc-searchbar-input:focus {
6469
outline: none;
6570
}
6671

6772
#scaladoc-searchbar-results {
68-
background: var(--white);
6973
display: flex;
7074
flex-direction: column;
7175
max-height: 500px;
7276
overflow: auto;
7377
}
7478

7579
.scaladoc-searchbar-result {
76-
background: var(--white);
80+
background-color: var(--leftbar-bg);
81+
color: var(--leftbar-fg);
7782
line-height: 24px;
7883
display: flex;
7984
padding: 4px 10px 4px 10px;
@@ -90,11 +95,11 @@
9095
}
9196

9297
.scaladoc-searchbar-result[selected] {
93-
background-color: var(--blue100);
98+
background-color: var(--leftbar-hover-bg);
99+
color: var(--leftbar-hover-fg);
94100
}
95101

96102
.scaladoc-searchbar-result a {
97-
color: var(--grey900);
98103
/* for some reason, with display:block if there's a wrap between the
99104
* search result text and the location span, the dead space to the
100105
* left of the location span doesn't get treated as part of the block,
@@ -107,10 +112,6 @@
107112
padding-left: 20px;
108113
}
109114

110-
.scaladoc-searchbar-result .scaladoc-searchbar-location {
111-
color: gray;
112-
}
113-
114115
#searchBar {
115116
display: inline-flex;
116117
}

scaladoc-js/src/searchbar/Searchbar.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package dotty.tools.scaladoc
22

33
class Searchbar {
44
val pages = SearchbarGlobals.pages.toList.map(PageEntry.apply)
5-
val engine = SearchbarEngine(pages)
65
val parser = QueryParser()
7-
val component = SearchbarComponent(q => engine.query(parser.parse(q)))
8-
}
6+
val searchEngine = SearchbarEngine(pages)
7+
val inkuireEngine = InkuireJSSearchEngine()
8+
val component = SearchbarComponent(searchEngine, inkuireEngine, parser)
9+
}

scaladoc-js/src/searchbar/SearchbarComponent.scala

+55-12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package dotty.tools.scaladoc
22

33
import org.scalajs.dom._
44
import org.scalajs.dom.html.Input
5+
import scala.scalajs.js.timers._
6+
import scala.concurrent.duration._
57

6-
class SearchbarComponent(val callback: (String) => List[PageEntry]):
8+
class SearchbarComponent(engine: SearchbarEngine, inkuireEngine: InkuireJSSearchEngine, parser: QueryParser):
79
val resultsChunkSize = 100
810
extension (p: PageEntry)
9-
def toHTML =
11+
def toHTML(inkuire: Boolean = false) =
1012
val wrapper = document.createElement("div").asInstanceOf[html.Div]
1113
wrapper.classList.add("scaladoc-searchbar-result")
1214
wrapper.classList.add("monospace")
@@ -16,7 +18,7 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]):
1618
icon.classList.add(p.kind.take(2))
1719

1820
val resultA = document.createElement("a").asInstanceOf[html.Anchor]
19-
resultA.href = Globals.pathToRoot + p.location
21+
resultA.href = if inkuire then p.location else Globals.pathToRoot + p.location
2022
resultA.text = s"${p.fullName}"
2123

2224
val location = document.createElement("span")
@@ -32,26 +34,67 @@ class SearchbarComponent(val callback: (String) => List[PageEntry]):
3234
})
3335
wrapper
3436

35-
def handleNewQuery(query: String) =
36-
val result = callback(query).map(_.toHTML)
37+
def handleNewFluffQuery(matchers: List[Matchers]) =
38+
val result = engine.query(matchers).map(_.toHTML(inkuire = false))
3739
resultsDiv.scrollTop = 0
3840
while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
3941
val fragment = document.createDocumentFragment()
4042
result.take(resultsChunkSize).foreach(fragment.appendChild)
4143
resultsDiv.appendChild(fragment)
4244
def loadMoreResults(result: List[raw.HTMLElement]): Unit = {
4345
resultsDiv.onscroll = (event: Event) => {
44-
if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight)
45-
{
46-
val fragment = document.createDocumentFragment()
47-
result.take(resultsChunkSize).foreach(fragment.appendChild)
48-
resultsDiv.appendChild(fragment)
49-
loadMoreResults(result.drop(resultsChunkSize))
50-
}
46+
if (resultsDiv.scrollHeight - resultsDiv.scrollTop == resultsDiv.clientHeight) {
47+
val fragment = document.createDocumentFragment()
48+
result.take(resultsChunkSize).foreach(fragment.appendChild)
49+
resultsDiv.appendChild(fragment)
50+
loadMoreResults(result.drop(resultsChunkSize))
51+
}
5152
}
5253
}
5354
loadMoreResults(result.drop(resultsChunkSize))
5455

56+
extension (s: String)
57+
def toHTMLError =
58+
val wrapper = document.createElement("div").asInstanceOf[html.Div]
59+
wrapper.classList.add("scaladoc-searchbar-result")
60+
wrapper.classList.add("monospace")
61+
62+
val errorSpan = document.createElement("span").asInstanceOf[html.Span]
63+
errorSpan.classList.add("search-error")
64+
errorSpan.textContent = s
65+
66+
wrapper.appendChild(errorSpan)
67+
wrapper
68+
69+
var timeoutHandle: SetTimeoutHandle = null
70+
def handleNewQuery(query: String) =
71+
clearTimeout(timeoutHandle)
72+
resultsDiv.scrollTop = 0
73+
resultsDiv.onscroll = (event: Event) => { }
74+
while (resultsDiv.hasChildNodes()) resultsDiv.removeChild(resultsDiv.lastChild)
75+
val fragment = document.createDocumentFragment()
76+
parser.parse(query) match {
77+
case EngineMatchersQuery(matchers) =>
78+
handleNewFluffQuery(matchers)
79+
case BySignature(signature) =>
80+
timeoutHandle = setTimeout(1.second) {
81+
val properResultsDiv = document.createElement("div").asInstanceOf[html.Div]
82+
resultsDiv.appendChild(properResultsDiv)
83+
val loading = document.createElement("div").asInstanceOf[html.Div]
84+
loading.classList.add("loading-wrapper")
85+
val animation = document.createElement("div").asInstanceOf[html.Div]
86+
animation.classList.add("loading")
87+
loading.appendChild(animation)
88+
properResultsDiv.appendChild(loading)
89+
inkuireEngine.query(query) { (p: PageEntry) =>
90+
properResultsDiv.appendChild(p.toHTML(inkuire = true))
91+
} { (s: String) =>
92+
animation.classList.remove("loading")
93+
properResultsDiv.appendChild(s.toHTMLError)
94+
}
95+
}
96+
}
97+
5598
private val searchIcon: html.Div =
5699
val span = document.createElement("span").asInstanceOf[html.Span]
57100
span.innerHTML = """<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M19.64 18.36l-6.24-6.24a7.52 7.52 0 10-1.28 1.28l6.24 6.24zM7.5 13.4a5.9 5.9 0 115.9-5.9 5.91 5.91 0 01-5.9 5.9z"></path></svg>"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dotty.tools.scaladoc
2+
3+
import scala.io.Source
4+
import dotty.tools.scaladoc.PageEntry
5+
import org.scalajs.dom.webworkers.Worker
6+
import org.scalajs.dom._
7+
import scala.scalajs.js.{ JSON, Dynamic }
8+
import scala.collection.mutable.ListBuffer
9+
import scala.scalajs.js
10+
import scala.scalajs.js.timers._
11+
import org.scalajs.dom.ext.Ajax
12+
import scala.scalajs.js.URIUtils
13+
14+
class InkuireJSSearchEngine {
15+
16+
val scriptPath = Globals.pathToRoot + "scripts/"
17+
val worker: Worker = new Worker(scriptPath + "inkuire-worker.js")
18+
19+
def dynamicToPageEntry(d: Dynamic): PageEntry = {
20+
PageEntry(
21+
d.functionName.asInstanceOf[String],
22+
d.prettifiedSignature.asInstanceOf[String],
23+
d.pageLocation.asInstanceOf[String],
24+
d.functionName.asInstanceOf[String],
25+
"def",
26+
List.empty
27+
)
28+
}
29+
30+
def query(s: String)(callback: PageEntry => Unit)(endCallback: String => Unit): List[PageEntry] = {
31+
worker.onmessage = _ => ()
32+
val res = ListBuffer[PageEntry]()
33+
val func = (msg: MessageEvent) => {
34+
msg.data.asInstanceOf[String] match {
35+
case "engine_ready" =>
36+
case "new_query" =>
37+
case endMsg if endMsg.startsWith("query_ended") =>
38+
endCallback(endMsg.drop("query_ended".length))
39+
case q =>
40+
val matches = JSON.parse(q).matches
41+
val actualMatches = matches.asInstanceOf[js.Array[Dynamic]].map(dynamicToPageEntry)
42+
actualMatches.foreach(callback)
43+
}
44+
}
45+
worker.onmessage = func
46+
worker.postMessage(s)
47+
res.toList
48+
}
49+
50+
}

scaladoc-js/src/searchbar/engine/Matchers.scala

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package dotty.tools.scaladoc
22

3+
sealed trait EngineQuery
4+
case class EngineMatchersQuery(matchers: List[Matchers]) extends EngineQuery
5+
case class BySignature(signature: String) extends EngineQuery
6+
37
sealed trait Matchers extends Function1[PageEntry, Int]
48

59
case class ByName(query: String) extends Matchers:

scaladoc-js/src/searchbar/engine/QueryParser.scala

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ class QueryParser:
1919
val kindRegex = ("(?i)" + kinds.mkString("(","|",")") + " (.*)").r
2020
val restRegex = raw"(.*)".r
2121
val escapedRegex = raw"`(.*)`".r
22+
val signatureRegex = raw"([^=>]+=>.*)".r
2223

23-
def parse(query: String): List[Matchers] = query match {
24+
def parseMatchers(query: String): List[Matchers] = query match {
2425
case escapedRegex(rest) => List(ByName(rest))
25-
case kindRegex(kind, rest) => List(ByKind(kind)) ++ parse(rest)
26+
case kindRegex(kind, rest) => List(ByKind(kind)) ++ parseMatchers(rest)
2627
case restRegex(name) => List(ByName(name))
2728
case _ => List()
29+
}
30+
31+
def parse(query: String): EngineQuery = query match {
32+
case signatureRegex(signature) => BySignature(signature)
33+
case other => EngineMatchersQuery(parseMatchers(other))
2834
}

scaladoc-js/src/searchbar/engine/SearchbarEngine.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dotty.tools.scaladoc
22

33
import math.Ordering.Implicits.seqOrdering
4+
import org.scalajs.dom.Node
45

56
class SearchbarEngine(pages: List[PageEntry]):
67
def query(query: List[Matchers]): List[PageEntry] =

scaladoc-js/test/dotty/dokka/QueryParserTest.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class QueryParserTest:
1717
"given",
1818
"type"
1919
)
20-
private def testCase(query: String, result: List[Matchers]) = {
20+
private def testCase(query: String, result: EngineQuery) = {
2121
val parsed = queryParser.parse(query)
2222
assertEquals(
2323
s"Query parser test error: for query: $query expected $result but found $parsed",
@@ -28,8 +28,8 @@ class QueryParserTest:
2828

2929
@Test
3030
def queryParserTests() = {
31-
kinds.foreach(k => testCase(s"$k ", List(ByKind(k), ByName(""))))
32-
testCase("trait", List(ByName("trait")))
33-
testCase("trait A", List(ByKind("trait"), ByName("A")))
34-
testCase("`trait A`", List(ByName("trait A")))
31+
kinds.foreach(k => testCase(s"$k ", EngineMatchersQuery(List(ByKind(k), ByName("")))))
32+
testCase("trait", EngineMatchersQuery(List(ByName("trait"))))
33+
testCase("trait A", EngineMatchersQuery(List(ByKind("trait"), ByName("A"))))
34+
testCase("`trait A`", EngineMatchersQuery(List(ByName("trait A"))))
3535
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tests.inkuire
2+
3+
trait InType1
4+
trait InType2 extends InType1
5+
6+
trait OutType1
7+
trait OutType2 extends OutType1
8+
9+
class JustAClass {
10+
def mathod(l: InType1): OutType1 = ???
11+
}
12+
13+
class JustAnotherClass extends JustAClass {
14+
def method(i: InType2): OutType2 = ???
15+
}

0 commit comments

Comments
 (0)