forked from scalameta/metals
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Docstrings.scala
147 lines (134 loc) Β· 4.48 KB
/
Docstrings.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package scala.meta.internal.metals
import java.util.Optional
import java.util.logging.Level
import java.util.logging.Logger
import scala.collection.concurrent.TrieMap
import scala.util.control.NonFatal
import scala.meta.Dialect
import scala.meta.inputs.Input
import scala.meta.internal.jdk.CollectionConverters._
import scala.meta.internal.mtags.GlobalSymbolIndex
import scala.meta.internal.mtags.OnDemandSymbolIndex
import scala.meta.internal.mtags.ScalaMtags
import scala.meta.internal.mtags.ScalametaCommonEnrichments._
import scala.meta.internal.mtags.Symbol
import scala.meta.internal.mtags.SymbolDefinition
import scala.meta.internal.semanticdb.Language
import scala.meta.internal.semanticdb.SymbolInformation
import scala.meta.internal.semanticdb.SymbolOccurrence
import scala.meta.io.AbsolutePath
import scala.meta.pc.ParentSymbols
import scala.meta.pc.SymbolDocumentation
/**
* Implementation of the `documentation(symbol: String): Option[SymbolDocumentation]` method in `SymbolSearch`.
*
* Handles both javadoc and scaladoc.
*/
class Docstrings(index: GlobalSymbolIndex)(implicit rc: ReportContext) {
val cache = new TrieMap[String, SymbolDocumentation]()
private val logger = Logger.getLogger(classOf[Docstrings].getName)
def documentation(
symbol: String,
parents: ParentSymbols
): Optional[SymbolDocumentation] = {
cache.get(symbol) match {
case Some(value) =>
if (value == EmptySymbolDocumentation) Optional.empty()
else Optional.of(value)
case None =>
indexSymbol(symbol)
val result = cache.get(symbol)
val resultWithDocs = result match {
case None =>
cache(symbol) = EmptySymbolDocumentation
result
/* Fall back to parent scaladocs if nothing is specified for the current symbol
* This way we also cache the result in order not to calculate parents again.
*/
case Some(value: MetalsSymbolDocumentation)
if value.docstring.isEmpty() =>
parents
.parents()
.asScala
.flatMap { s =>
if (cache.contains(s)) cache.get(s)
else {
indexSymbol(s)
cache.get(s)
}
}
.find(_.docstring().nonEmpty)
.fold {
result
} { withDocs =>
val updated = value.copy(docstring = withDocs.docstring())
cache(symbol) = updated
Some(updated)
}
case _ =>
result
}
Optional.ofNullable(resultWithDocs.orNull)
}
}
/**
* Expire all symbols showed in the given scala source file.
*
* Note that what this method does is only expiring the cache, and
* it doesn't update the cache in honor of the memory footprint.
* Otherwise, if we update the cache for symbols every time we save a file,
* metals will cache all symbols in files we've saved, and it consumes a considerable amount of memory.
*
* @param path the absolute path for the source file to update.
*/
def expireSymbolDefinition(path: AbsolutePath, dialect: Dialect): Unit = {
path.toLanguage match {
case Language.SCALA =>
new Deindexer(path.toInput, dialect).indexRoot()
case _ =>
}
}
private def cacheSymbol(doc: SymbolDocumentation): Unit = {
cache(doc.symbol()) = doc
}
private def indexSymbol(symbol: String): Unit = {
index.definition(Symbol(symbol)) match {
case Some(defn) =>
try {
indexSymbolDefinition(defn)
} catch {
case NonFatal(e) =>
logger.log(Level.SEVERE, defn.path.toURI.toString, e)
}
case None =>
}
}
private def indexSymbolDefinition(defn: SymbolDefinition): Unit = {
defn.path.toLanguage match {
case Language.JAVA =>
JavadocIndexer
.foreach(defn.path.toInput)(cacheSymbol)
case Language.SCALA =>
ScaladocIndexer
.foreach(defn.path.toInput, defn.dialect)(cacheSymbol)
case _ =>
}
}
private class Deindexer(
input: Input.VirtualFile,
dialect: Dialect
) extends ScalaMtags(input, dialect) {
override def visitOccurrence(
occ: SymbolOccurrence,
sinfo: SymbolInformation,
owner: String
): Unit = {
cache.remove(occ.symbol)
}
}
}
object Docstrings {
def empty(implicit rc: ReportContext): Docstrings = new Docstrings(
OnDemandSymbolIndex.empty()
)
}